From 2ff073532971e5ea33ef49f84d9a6178550ec0ce Mon Sep 17 00:00:00 2001 From: ConteZero Date: Wed, 20 Oct 2021 16:19:17 +0200 Subject: [PATCH] Added primary clipboard for Linux --- core/os/os.cpp | 8 +++++ core/os/os.h | 3 ++ doc/classes/LineEdit.xml | 4 +++ doc/classes/TextEdit.xml | 4 +++ main/main.cpp | 1 + platform/x11/os_x11.cpp | 55 +++++++++++++++++++++++++++++++---- platform/x11/os_x11.h | 4 ++- scene/gui/line_edit.cpp | 41 ++++++++++++++++++++++++++ scene/gui/line_edit.h | 4 +++ scene/gui/rich_text_label.cpp | 7 +++++ scene/gui/text_edit.cpp | 41 ++++++++++++++++++++++++++ scene/gui/text_edit.h | 5 ++++ 12 files changed, 170 insertions(+), 7 deletions(-) diff --git a/core/os/os.cpp b/core/os/os.cpp index 51040a993f88..580bf09995b7 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -170,6 +170,14 @@ bool OS::has_clipboard() const { return !get_clipboard().empty(); } +void OS::set_clipboard_primary(const String &p_text) { + _primary_clipboard = p_text; +} + +String OS::get_clipboard_primary() const { + return _primary_clipboard; +} + String OS::get_executable_path() const { return _execpath; } diff --git a/core/os/os.h b/core/os/os.h index 5209f40c2dcf..5d68ea645740 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -55,6 +55,7 @@ class OS { bool _verbose_stdout; bool _debug_stdout; String _local_clipboard; + String _primary_clipboard; uint64_t _msec_splash; bool _no_window; int _exit_code = EXIT_FAILURE; // unexpected exit is marked as failure @@ -179,6 +180,8 @@ class OS { virtual void set_clipboard(const String &p_text); virtual String get_clipboard() const; virtual bool has_clipboard() const; + virtual void set_clipboard_primary(const String &p_text); + virtual String get_clipboard_primary() const; virtual void set_video_mode(const VideoMode &p_video_mode, int p_screen = 0) = 0; virtual VideoMode get_video_mode(int p_screen = 0) const = 0; diff --git a/doc/classes/LineEdit.xml b/doc/classes/LineEdit.xml index 1b02ea02f2cf..28b32a21bf32 100644 --- a/doc/classes/LineEdit.xml +++ b/doc/classes/LineEdit.xml @@ -145,6 +145,10 @@ # `text_change_rejected` is emitted with "bye" as parameter. [/codeblock] + + If [code]false[/code], using middle mouse button to paste clipboard will be disabled. + [b]Note:[/b] This method is only implemented on Linux. + Opacity of the [member placeholder_text]. From [code]0[/code] to [code]1[/code]. diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml index 0f0218da1d6a..9dace1d8540a 100644 --- a/doc/classes/TextEdit.xml +++ b/doc/classes/TextEdit.xml @@ -514,6 +514,10 @@ If [code]true[/code], the line containing the cursor is highlighted. + + If [code]false[/code], using middle mouse button to paste clipboard will be disabled. + [b]Note:[/b] This method is only implemented on Linux. + If [code]true[/code], a minimap is shown, providing an outline of your source code. diff --git a/main/main.cpp b/main/main.cpp index ad802d01d551..c7da6feba354 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -2416,6 +2416,7 @@ void Main::cleanup(bool p_force) { OS::get_singleton()->_cmdline.clear(); OS::get_singleton()->_execpath = ""; OS::get_singleton()->_local_clipboard = ""; + OS::get_singleton()->_primary_clipboard = ""; ResourceLoader::clear_translation_remaps(); ResourceLoader::clear_path_remaps(); diff --git a/platform/x11/os_x11.cpp b/platform/x11/os_x11.cpp index 7127447f140f..3899facaebab 100644 --- a/platform/x11/os_x11.cpp +++ b/platform/x11/os_x11.cpp @@ -2177,7 +2177,7 @@ void OS_X11::_handle_key_event(XKeyEvent *p_event, LocalVector &p_events input->parse_input_event(k); } -Atom OS_X11::_process_selection_request_target(Atom p_target, Window p_requestor, Atom p_property) const { +Atom OS_X11::_process_selection_request_target(Atom p_target, Window p_requestor, Atom p_property, Atom p_selection) const { if (p_target == XInternAtom(x11_display, "TARGETS", 0)) { // Request to list all supported targets. Atom data[9]; @@ -2220,7 +2220,13 @@ Atom OS_X11::_process_selection_request_target(Atom p_target, Window p_requestor p_target == XInternAtom(x11_display, "text/plain", 0)) { // Directly using internal clipboard because we know our window // is the owner during a selection request. - CharString clip = OS::get_clipboard().utf8(); + CharString clip; + static const char *target_type = "PRIMARY"; + if (p_selection != None && String(XGetAtomName(x11_display, p_selection)) == target_type) { + clip = OS::get_clipboard_primary().utf8(); + } else { + clip = OS::get_clipboard().utf8(); + } XChangeProperty(x11_display, p_requestor, p_property, @@ -2258,7 +2264,7 @@ void OS_X11::_handle_selection_request_event(XSelectionRequestEvent *p_event) co for (uint64_t i = 0; i < len; i += 2) { Atom target = targets[i]; Atom &property = targets[i + 1]; - property = _process_selection_request_target(target, p_event->requestor, property); + property = _process_selection_request_target(target, p_event->requestor, property, p_event->selection); } XChangeProperty(x11_display, @@ -2276,7 +2282,7 @@ void OS_X11::_handle_selection_request_event(XSelectionRequestEvent *p_event) co } } else { // Request for target conversion. - respond.xselection.property = _process_selection_request_target(p_event->target, p_event->requestor, p_event->property); + respond.xselection.property = _process_selection_request_target(p_event->target, p_event->requestor, p_event->property, p_event->selection); } respond.xselection.type = SelectionNotify; @@ -3112,7 +3118,12 @@ String OS_X11::_get_clipboard_impl(Atom p_source, Window x11_window, Atom target Window selection_owner = XGetSelectionOwner(x11_display, p_source); if (selection_owner == x11_window) { - return OS::get_clipboard(); + static const char *target_type = "PRIMARY"; + if (p_source != None && String(XGetAtomName(x11_display, p_source)) == target_type) { + return OS::get_clipboard_primary(); + } else { + return OS::get_clipboard(); + } } if (selection_owner != None) { @@ -3315,6 +3326,30 @@ void OS_X11::_clipboard_transfer_ownership(Atom p_source, Window x11_window) con } } +void OS_X11::set_clipboard_primary(const String &p_text) { + if (!p_text.empty()) { + { + // The clipboard content can be accessed while polling for events. + MutexLock mutex_lock(events_mutex); + OS::set_clipboard_primary(p_text); + } + + XSetSelectionOwner(x11_display, XA_PRIMARY, x11_window, CurrentTime); + XSetSelectionOwner(x11_display, XInternAtom(x11_display, "PRIMARY", 0), x11_window, CurrentTime); + } +} + +String OS_X11::get_clipboard_primary() const { + String ret; + ret = _get_clipboard(XInternAtom(x11_display, "PRIMARY", 0), x11_window); + + if (ret.empty()) { + ret = _get_clipboard(XA_PRIMARY, x11_window); + } + + return ret; +} + String OS_X11::get_name() const { return "X11"; } @@ -3357,7 +3392,15 @@ Error OS_X11::shell_open(String p_uri) { } bool OS_X11::_check_internal_feature_support(const String &p_feature) { - return p_feature == "pc"; + if (p_feature == "pc") { + return true; + } + + if (p_feature == "primary_clipboard") { + return true; + } + + return false; } String OS_X11::get_config_path() const { diff --git a/platform/x11/os_x11.h b/platform/x11/os_x11.h index bed55903060b..cbf68b17603a 100644 --- a/platform/x11/os_x11.h +++ b/platform/x11/os_x11.h @@ -157,7 +157,7 @@ class OS_X11 : public OS_Unix { void _handle_key_event(XKeyEvent *p_event, LocalVector &p_events, uint32_t &p_event_index, bool p_echo = false); - Atom _process_selection_request_target(Atom p_target, Window p_requestor, Atom p_property) const; + Atom _process_selection_request_target(Atom p_target, Window p_requestor, Atom p_property, Atom p_selection) const; void _handle_selection_request_event(XSelectionRequestEvent *p_event) const; String _get_clipboard_impl(Atom p_source, Window x11_window, Atom target) const; @@ -273,6 +273,8 @@ class OS_X11 : public OS_Unix { virtual void set_clipboard(const String &p_text); virtual String get_clipboard() const; + virtual void set_clipboard_primary(const String &p_text); + virtual String get_clipboard_primary() const; virtual void release_rendering_thread(); virtual void make_rendering_thread(); diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 3a509a130d95..81de06386f03 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -67,6 +67,26 @@ void LineEdit::_gui_input(Ref p_event) { return; } + if (is_middle_mouse_paste_enabled() && b->is_pressed() && b->get_button_index() == BUTTON_MIDDLE && is_editable() && OS::get_singleton()->has_feature("primary_clipboard")) { + String paste_buffer = OS::get_singleton()->get_clipboard_primary().strip_escapes(); + + selection.enabled = false; + set_cursor_at_pixel_pos(b->get_position().x); + if (!paste_buffer.empty()) { + append_at_cursor(paste_buffer); + + if (!text_changed_dirty) { + if (is_inside_tree()) { + MessageQueue::get_singleton()->push_call(this, "_text_changed"); + } + text_changed_dirty = true; + } + } + + grab_focus(); + return; + } + if (b->get_button_index() != BUTTON_LEFT) { return; } @@ -100,6 +120,9 @@ void LineEdit::_gui_input(Ref p_event) { selection.end = text.length(); selection.doubleclick = true; selection.last_dblclk = 0; + if (!pass && OS::get_singleton()->has_feature("primary_clipboard")) { + OS::get_singleton()->set_clipboard_primary(text); + } } else if (b->is_doubleclick()) { // Double-click select word. selection.enabled = true; @@ -119,6 +142,9 @@ void LineEdit::_gui_input(Ref p_event) { selection.end = end; selection.doubleclick = true; selection.last_dblclk = OS::get_singleton()->get_ticks_msec(); + if (!pass && OS::get_singleton()->has_feature("primary_clipboard")) { + OS::get_singleton()->set_clipboard_primary(text.substr(selection.begin, selection.end - selection.begin)); + } } } @@ -136,6 +162,9 @@ void LineEdit::_gui_input(Ref p_event) { update(); } else { + if (selection.enabled && !pass && b->get_button_index() == BUTTON_LEFT && OS::get_singleton()->has_feature("primary_clipboard")) { + OS::get_singleton()->set_clipboard_primary(text.substr(selection.begin, selection.end - selection.begin)); + } if (!text.empty() && is_editable() && clear_button_enabled) { bool press_attempt = clear_button_status.press_attempt; clear_button_status.press_attempt = false; @@ -1779,6 +1808,14 @@ bool LineEdit::is_virtual_keyboard_enabled() const { return virtual_keyboard_enabled; } +void LineEdit::set_middle_mouse_paste_enabled(bool p_enabled) { + middle_mouse_paste_enabled = p_enabled; +} + +bool LineEdit::is_middle_mouse_paste_enabled() const { + return middle_mouse_paste_enabled; +} + void LineEdit::set_selecting_enabled(bool p_enabled) { selecting_enabled = p_enabled; @@ -1955,6 +1992,8 @@ void LineEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("is_clear_button_enabled"), &LineEdit::is_clear_button_enabled); ClassDB::bind_method(D_METHOD("set_shortcut_keys_enabled", "enable"), &LineEdit::set_shortcut_keys_enabled); ClassDB::bind_method(D_METHOD("is_shortcut_keys_enabled"), &LineEdit::is_shortcut_keys_enabled); + ClassDB::bind_method(D_METHOD("set_middle_mouse_paste_enabled", "enable"), &LineEdit::set_middle_mouse_paste_enabled); + ClassDB::bind_method(D_METHOD("is_middle_mouse_paste_enabled"), &LineEdit::is_middle_mouse_paste_enabled); ClassDB::bind_method(D_METHOD("set_selecting_enabled", "enable"), &LineEdit::set_selecting_enabled); ClassDB::bind_method(D_METHOD("is_selecting_enabled"), &LineEdit::is_selecting_enabled); ClassDB::bind_method(D_METHOD("set_deselect_on_focus_loss_enabled", "enable"), &LineEdit::set_deselect_on_focus_loss_enabled); @@ -1991,6 +2030,7 @@ void LineEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "virtual_keyboard_enabled"), "set_virtual_keyboard_enabled", "is_virtual_keyboard_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clear_button_enabled"), "set_clear_button_enabled", "is_clear_button_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_keys_enabled"), "set_shortcut_keys_enabled", "is_shortcut_keys_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "middle_mouse_paste_enabled"), "set_middle_mouse_paste_enabled", "is_middle_mouse_paste_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selecting_enabled"), "set_selecting_enabled", "is_selecting_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deselect_on_focus_loss_enabled"), "set_deselect_on_focus_loss_enabled", "is_deselect_on_focus_loss_enabled"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "right_icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_right_icon", "get_right_icon"); @@ -2019,6 +2059,7 @@ LineEdit::LineEdit() { clear_button_status.press_attempt = false; clear_button_status.pressing_inside = false; shortcut_keys_enabled = true; + middle_mouse_paste_enabled = true; selecting_enabled = true; deselect_on_focus_loss_enabled = true; diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index 758b75fd33b3..19c5c60e28ff 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -95,6 +95,7 @@ class LineEdit : public Control { bool drag_action = false; bool drag_caret_force_displayed = false; + bool middle_mouse_paste_enabled; Ref right_icon; @@ -239,6 +240,9 @@ class LineEdit : public Control { void set_virtual_keyboard_enabled(bool p_enable); bool is_virtual_keyboard_enabled() const; + void set_middle_mouse_paste_enabled(bool p_enabled); + bool is_middle_mouse_paste_enabled() const; + void set_selecting_enabled(bool p_enabled); bool is_selecting_enabled() const; diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 6ef65bcbe9ff..240afcc277ce 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -1205,6 +1205,9 @@ void RichTextLabel::_gui_input(Ref p_event) { selection.from_char = beg; selection.to_char = end - 1; selection.active = true; + if (OS::get_singleton()->has_feature("primary_clipboard")) { + OS::get_singleton()->set_clipboard_primary(get_selected_text()); + } update(); } } @@ -1228,6 +1231,9 @@ void RichTextLabel::_gui_input(Ref p_event) { update(); } } + if (selection.enabled && OS::get_singleton()->has_feature("primary_clipboard")) { + OS::get_singleton()->set_clipboard_primary(get_selected_text()); + } selection.click = nullptr; if (!b->is_doubleclick() && !scroll_updated) { @@ -1359,6 +1365,7 @@ void RichTextLabel::_gui_input(Ref p_event) { swap = true; } else if (selection.from_char == selection.to_char) { selection.active = false; + update(); return; } } diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index db6842188a30..53fa404d0e61 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -502,6 +502,10 @@ void TextEdit::_update_selection_mode_word() { } } + if (OS::get_singleton()->has_feature("primary_clipboard")) { + OS::get_singleton()->set_clipboard_primary(get_selection_text()); + } + update(); click_select_held->start(); @@ -529,6 +533,9 @@ void TextEdit::_update_selection_mode_line() { cursor_set_column(0); select(selection.selecting_line, selection.selecting_column, row, col); + if (OS::get_singleton()->has_feature("primary_clipboard")) { + OS::get_singleton()->set_clipboard_primary(get_selection_text()); + } update(); click_select_held->start(); @@ -2602,6 +2609,25 @@ void TextEdit::_gui_input(const Ref &p_gui_input) { update(); } + if (is_middle_mouse_paste_enabled() && mb->get_button_index() == BUTTON_MIDDLE && !readonly && OS::get_singleton()->has_feature("primary_clipboard")) { + String paste_buffer = OS::get_singleton()->get_clipboard_primary(); + + int row, col; + _get_mouse_pos(Point2i(mb->get_position().x, mb->get_position().y), row, col); + begin_complex_operation(); + + deselect(); + cursor_set_line(row, true, false); + cursor_set_column(col); + if (!paste_buffer.empty()) { + _insert_text_at_cursor(paste_buffer); + } + end_complex_operation(); + + grab_focus(); + update(); + } + if (mb->get_button_index() == BUTTON_RIGHT && context_menu_enabled) { _reset_caret_blink_timer(); @@ -2657,6 +2683,9 @@ void TextEdit::_gui_input(const Ref &p_gui_input) { if (!drag_action) { selection.drag_attempt = false; } + if (OS::get_singleton()->has_feature("primary_clipboard")) { + OS::get_singleton()->set_clipboard_primary(get_selection_text()); + } } // Notify to show soft keyboard. @@ -7299,6 +7328,10 @@ void TextEdit::set_virtual_keyboard_enabled(bool p_enable) { virtual_keyboard_enabled = p_enable; } +void TextEdit::set_middle_mouse_paste_enabled(bool p_enabled) { + middle_mouse_paste_enabled = p_enabled; +} + void TextEdit::set_selecting_enabled(bool p_enabled) { selecting_enabled = p_enabled; @@ -7332,6 +7365,10 @@ bool TextEdit::is_virtual_keyboard_enabled() const { return virtual_keyboard_enabled; } +bool TextEdit::is_middle_mouse_paste_enabled() const { + return middle_mouse_paste_enabled; +} + PopupMenu *TextEdit::get_menu() const { return menu; } @@ -7430,6 +7467,8 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("is_shortcut_keys_enabled"), &TextEdit::is_shortcut_keys_enabled); ClassDB::bind_method(D_METHOD("set_virtual_keyboard_enabled", "enable"), &TextEdit::set_virtual_keyboard_enabled); ClassDB::bind_method(D_METHOD("is_virtual_keyboard_enabled"), &TextEdit::is_virtual_keyboard_enabled); + ClassDB::bind_method(D_METHOD("set_middle_mouse_paste_enabled", "enable"), &TextEdit::set_middle_mouse_paste_enabled); + ClassDB::bind_method(D_METHOD("is_middle_mouse_paste_enabled"), &TextEdit::is_middle_mouse_paste_enabled); ClassDB::bind_method(D_METHOD("set_selecting_enabled", "enable"), &TextEdit::set_selecting_enabled); ClassDB::bind_method(D_METHOD("is_selecting_enabled"), &TextEdit::is_selecting_enabled); ClassDB::bind_method(D_METHOD("set_deselect_on_focus_loss_enabled", "enable"), &TextEdit::set_deselect_on_focus_loss_enabled); @@ -7545,6 +7584,7 @@ void TextEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_keys_enabled"), "set_shortcut_keys_enabled", "is_shortcut_keys_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "virtual_keyboard_enabled"), "set_virtual_keyboard_enabled", "is_virtual_keyboard_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "middle_mouse_paste_enabled"), "set_middle_mouse_paste_enabled", "is_middle_mouse_paste_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selecting_enabled"), "set_selecting_enabled", "is_selecting_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deselect_on_focus_loss_enabled"), "set_deselect_on_focus_loss_enabled", "is_deselect_on_focus_loss_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_scrolling"), "set_smooth_scroll_enable", "is_smooth_scroll_enabled"); @@ -7715,6 +7755,7 @@ TextEdit::TextEdit() { deselect_on_focus_loss_enabled = true; context_menu_enabled = true; shortcut_keys_enabled = true; + middle_mouse_paste_enabled = true; menu = memnew(PopupMenu); add_child(menu); readonly = true; // Initialise to opposite first, so we get past the early-out in set_readonly. diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 8980ab79c07a..e6a4f15feaac 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -444,6 +444,8 @@ class TextEdit : public Control { bool virtual_keyboard_enabled = true; + bool middle_mouse_paste_enabled; + int executing_line; void _generate_context_menu(); @@ -864,6 +866,9 @@ class TextEdit : public Control { void set_virtual_keyboard_enabled(bool p_enable); bool is_virtual_keyboard_enabled() const; + void set_middle_mouse_paste_enabled(bool p_enabled); + bool is_middle_mouse_paste_enabled() const; + PopupMenu *get_menu() const; String get_text_for_completion();