diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index 7a467c03c1..4b43286594 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -13,6 +13,13 @@ Returns the user's clipboard as a string if possible. + + + + Returns the user's primary clipboard as a string if possible. + [b]Note:[/b] This method is only implemented on Linux. + + @@ -20,6 +27,14 @@ Sets the user's clipboard content to the given string. + + + + + Sets the user's primary clipboard content to the given string. + [b]Note:[/b] This method is only implemented on Linux. + + diff --git a/doc/classes/LineEdit.xml b/doc/classes/LineEdit.xml index 75f2d72396..a37bab9b11 100644 --- a/doc/classes/LineEdit.xml +++ b/doc/classes/LineEdit.xml @@ -222,6 +222,10 @@ [/csharp] [/codeblocks] + + 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 a3914b58df..ba3394b54a 100644 --- a/doc/classes/TextEdit.xml +++ b/doc/classes/TextEdit.xml @@ -41,6 +41,13 @@ Override this method to define what happens when the user performs a paste operation. + + + + Override this method to define what happens when the user performs a paste operation with middle mouse button. + [b]Note:[/b] This method is only implemented on Linux. + + @@ -937,6 +944,10 @@ Language code used for line-breaking and text shaping algorithms, if left empty current locale is used instead. + + 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/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp index f2cd336b39..c0e5aac938 100644 --- a/platform/linuxbsd/display_server_x11.cpp +++ b/platform/linuxbsd/display_server_x11.cpp @@ -406,6 +406,20 @@ void DisplayServerX11::clipboard_set(const String &p_text) { XSetSelectionOwner(x11_display, XInternAtom(x11_display, "CLIPBOARD", 0), windows[MAIN_WINDOW_ID].x11_window, CurrentTime); } +void DisplayServerX11::clipboard_set_primary(const String &p_text) { + _THREAD_SAFE_METHOD_ + if (!p_text.is_empty()) { + { + // The clipboard content can be accessed while polling for events. + MutexLock mutex_lock(events_mutex); + internal_clipboard_primary = p_text; + } + + XSetSelectionOwner(x11_display, XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window, CurrentTime); + XSetSelectionOwner(x11_display, XInternAtom(x11_display, "PRIMARY", 0), windows[MAIN_WINDOW_ID].x11_window, CurrentTime); + } +} + Bool DisplayServerX11::_predicate_clipboard_selection(Display *display, XEvent *event, XPointer arg) { if (event->type == SelectionNotify && event->xselection.requestor == *(Window *)arg) { return True; @@ -427,7 +441,12 @@ String DisplayServerX11::_clipboard_get_impl(Atom p_source, Window x11_window, A Window selection_owner = XGetSelectionOwner(x11_display, p_source); if (selection_owner == x11_window) { - return internal_clipboard; + static const char *target_type = "PRIMARY"; + if (p_source != None && String(XGetAtomName(x11_display, p_source)) == target_type) { + return internal_clipboard_primary; + } else { + return internal_clipboard; + } } if (selection_owner != None) { @@ -580,6 +599,19 @@ String DisplayServerX11::clipboard_get() const { return ret; } +String DisplayServerX11::clipboard_get_primary() const { + _THREAD_SAFE_METHOD_ + + String ret; + ret = _clipboard_get(XInternAtom(x11_display, "PRIMARY", 0), windows[MAIN_WINDOW_ID].x11_window); + + if (ret.is_empty()) { + ret = _clipboard_get(XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window); + } + + return ret; +} + Bool DisplayServerX11::_predicate_clipboard_save_targets(Display *display, XEvent *event, XPointer arg) { if (event->xany.window == *(Window *)arg) { return (event->type == SelectionRequest) || @@ -2417,7 +2449,7 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, Input::get_singleton()->parse_input_event(k); } -Atom DisplayServerX11::_process_selection_request_target(Atom p_target, Window p_requestor, Atom p_property) const { +Atom DisplayServerX11::_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]; @@ -2459,7 +2491,13 @@ Atom DisplayServerX11::_process_selection_request_target(Atom p_target, Window p 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 = internal_clipboard.utf8(); + CharString clip; + static const char *target_type = "PRIMARY"; + if (p_selection != None && String(XGetAtomName(x11_display, p_selection)) == target_type) { + clip = internal_clipboard_primary.utf8(); + } else { + clip = internal_clipboard.utf8(); + } XChangeProperty(x11_display, p_requestor, p_property, @@ -2497,7 +2535,7 @@ void DisplayServerX11::_handle_selection_request_event(XSelectionRequestEvent *p 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, @@ -2515,7 +2553,7 @@ void DisplayServerX11::_handle_selection_request_event(XSelectionRequestEvent *p } } 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; diff --git a/platform/linuxbsd/display_server_x11.h b/platform/linuxbsd/display_server_x11.h index 1887c7105b..ded481f613 100644 --- a/platform/linuxbsd/display_server_x11.h +++ b/platform/linuxbsd/display_server_x11.h @@ -155,6 +155,7 @@ class DisplayServerX11 : public DisplayServer { WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect); String internal_clipboard; + String internal_clipboard_primary; Window xdnd_source_window; ::Display *x11_display; char *xmbstring; @@ -205,7 +206,7 @@ class DisplayServerX11 : public DisplayServer { void _handle_key_event(WindowID p_window, 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 _clipboard_get_impl(Atom p_source, Window x11_window, Atom target) const; @@ -290,6 +291,8 @@ public: virtual void clipboard_set(const String &p_text) override; virtual String clipboard_get() const override; + virtual void clipboard_set_primary(const String &p_text) override; + virtual String clipboard_get_primary() const override; virtual int get_screen_count() const override; virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 54e7d8f960..9ea98174c9 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -235,6 +235,25 @@ void LineEdit::gui_input(const Ref &p_event) { return; } + if (is_middle_mouse_paste_enabled() && b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_MIDDLE && is_editable()) { + String paste_buffer = DisplayServer::get_singleton()->clipboard_get_primary().strip_escapes(); + + deselect(); + set_caret_at_pixel_pos(b->get_position().x); + if (!paste_buffer.is_empty()) { + insert_text_at_caret(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() != MOUSE_BUTTON_LEFT) { return; } @@ -271,6 +290,9 @@ void LineEdit::gui_input(const Ref &p_event) { selection.double_click = true; last_dblclk = 0; caret_column = selection.begin; + if (!pass) { + DisplayServer::get_singleton()->clipboard_set_primary(text); + } } else if (b->is_double_click()) { // Double-click select word. last_dblclk = OS::get_singleton()->get_ticks_msec(); @@ -286,6 +308,9 @@ void LineEdit::gui_input(const Ref &p_event) { break; } } + if (!pass) { + DisplayServer::get_singleton()->clipboard_set_primary(text.substr(selection.begin, selection.end - selection.begin)); + } } } @@ -303,6 +328,9 @@ void LineEdit::gui_input(const Ref &p_event) { update(); } else { + if (selection.enabled && !pass && b->get_button_index() == MOUSE_BUTTON_LEFT) { + DisplayServer::get_singleton()->clipboard_set_primary(text.substr(selection.begin, selection.end - selection.begin)); + } if (!text.is_empty() && is_editable() && clear_button_enabled) { bool press_attempt = clear_button_status.press_attempt; clear_button_status.press_attempt = false; @@ -1890,6 +1918,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; @@ -2156,6 +2192,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_right_icon", "icon"), &LineEdit::set_right_icon); @@ -2211,6 +2249,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::OBJECT, "right_icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_right_icon", "get_right_icon"); ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index 923024dd56..cd7737e5fe 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -126,6 +126,8 @@ private: bool virtual_keyboard_enabled = true; + bool middle_mouse_paste_enabled = true; + Ref right_icon; struct Selection { @@ -318,6 +320,9 @@ public: 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 93d1eb5ba4..bc6552c208 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -1596,12 +1596,16 @@ void RichTextLabel::gui_input(const Ref &p_event) { selection.to_char = words[i + 1]; selection.active = true; + DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text()); update(); break; } } } } else if (!b->is_pressed()) { + if (selection.enabled) { + DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text()); + } selection.click_item = nullptr; if (!b->is_double_click() && !scroll_updated) { @@ -1719,6 +1723,7 @@ void RichTextLabel::gui_input(const 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 55c18980f9..9899b02dde 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -1534,6 +1534,10 @@ void TextEdit::gui_input(const Ref &p_gui_input) { update(); } + if (is_middle_mouse_paste_enabled() && mb->get_button_index() == MOUSE_BUTTON_MIDDLE) { + paste_primary_clipboard(); + } + if (mb->get_button_index() == MOUSE_BUTTON_RIGHT && context_menu_enabled) { _reset_caret_blink_timer(); @@ -1571,6 +1575,7 @@ void TextEdit::gui_input(const Ref &p_gui_input) { dragging_selection = false; can_drag_minimap = false; click_select_held->stop(); + DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text()); } // Notify to show soft keyboard. @@ -2596,6 +2601,14 @@ bool TextEdit::is_virtual_keyboard_enabled() const { return virtual_keyboard_enabled; } +void TextEdit::set_middle_mouse_paste_enabled(bool p_enabled) { + middle_mouse_paste_enabled = p_enabled; +} + +bool TextEdit::is_middle_mouse_paste_enabled() const { + return middle_mouse_paste_enabled; +} + // Text manipulation void TextEdit::clear() { setting_text = true; @@ -2915,6 +2928,13 @@ void TextEdit::paste() { _paste_internal(); } +void TextEdit::paste_primary_clipboard() { + if (GDVIRTUAL_CALL(_paste_primary_clipboard)) { + return; + } + _paste_primary_clipboard_internal(); +} + // Context menu. PopupMenu *TextEdit::get_menu() const { const_cast(this)->_generate_context_menu(); @@ -4536,6 +4556,9 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_virtual_keyboard_enabled", "enabled"), &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", "enabled"), &TextEdit::set_middle_mouse_paste_enabled); + ClassDB::bind_method(D_METHOD("is_middle_mouse_paste_enabled"), &TextEdit::is_middle_mouse_paste_enabled); + // Text manipulation ClassDB::bind_method(D_METHOD("clear"), &TextEdit::clear); @@ -4575,6 +4598,7 @@ void TextEdit::_bind_methods() { GDVIRTUAL_BIND(_cut) GDVIRTUAL_BIND(_copy) GDVIRTUAL_BIND(_paste) + GDVIRTUAL_BIND(_paste_primary_clipboard) // Context Menu BIND_ENUM_CONSTANT(MENU_CUT); @@ -4850,6 +4874,7 @@ void TextEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_keys_enabled"), "set_shortcut_keys_enabled", "is_shortcut_keys_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selecting_enabled"), "set_selecting_enabled", "is_selecting_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::INT, "wrap_mode", PROPERTY_HINT_ENUM, "None,Boundary"), "set_line_wrapping_mode", "get_line_wrapping_mode"); @@ -5141,6 +5166,24 @@ void TextEdit::_paste_internal() { end_complex_operation(); } +void TextEdit::_paste_primary_clipboard_internal() { + if (!is_editable()) { + return; + } + + String paste_buffer = DisplayServer::get_singleton()->clipboard_get_primary(); + + Point2i pos = get_line_column_at_pos(get_local_mouse_pos()); + deselect(); + set_caret_line(pos.y, true, false); + set_caret_column(pos.x); + if (!paste_buffer.is_empty()) { + insert_text_at_caret(paste_buffer); + } + + grab_focus(); +} + /* Text. */ // Context menu. void TextEdit::_generate_context_menu() { @@ -5477,6 +5520,8 @@ void TextEdit::_update_selection_mode_word() { } } + DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text()); + update(); click_select_held->start(); @@ -5504,6 +5549,8 @@ void TextEdit::_update_selection_mode_line() { set_caret_column(0); select(selection.selecting_line, selection.selecting_column, line, col); + DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text()); + update(); click_select_held->start(); diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 16c1ee9ff9..8823e44c0d 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -269,6 +269,7 @@ private: bool context_menu_enabled = true; bool shortcut_keys_enabled = true; bool virtual_keyboard_enabled = true; + bool middle_mouse_paste_enabled = true; // Overridable actions String cut_copy_line = ""; @@ -586,12 +587,14 @@ protected: virtual void _cut_internal(); virtual void _copy_internal(); virtual void _paste_internal(); + virtual void _paste_primary_clipboard_internal(); GDVIRTUAL1(_handle_unicode_input, int) GDVIRTUAL0(_backspace) GDVIRTUAL0(_cut) GDVIRTUAL0(_copy) GDVIRTUAL0(_paste) + GDVIRTUAL0(_paste_primary_clipboard) public: /* General overrides. */ @@ -640,6 +643,9 @@ public: void set_virtual_keyboard_enabled(bool p_enabled); bool is_virtual_keyboard_enabled() const; + void set_middle_mouse_paste_enabled(bool p_enabled); + bool is_middle_mouse_paste_enabled() const; + // Text manipulation void clear(); @@ -674,6 +680,7 @@ public: void cut(); void copy(); void paste(); + void paste_primary_clipboard(); // Context menu. PopupMenu *get_menu() const; diff --git a/servers/display_server.cpp b/servers/display_server.cpp index 3897e5e7c2..a6101530c8 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -159,6 +159,14 @@ String DisplayServer::clipboard_get() const { ERR_FAIL_V_MSG(String(), "Clipboard is not supported by this display server."); } +void DisplayServer::clipboard_set_primary(const String &p_text) { + WARN_PRINT("Primary clipboard is not supported by this display server."); +} + +String DisplayServer::clipboard_get_primary() const { + ERR_FAIL_V_MSG(String(), "Primary clipboard is not supported by this display server."); +} + void DisplayServer::screen_set_orientation(ScreenOrientation p_orientation, int p_screen) { WARN_PRINT("Orientation not supported by this display server."); } @@ -360,6 +368,8 @@ void DisplayServer::_bind_methods() { ClassDB::bind_method(D_METHOD("clipboard_set", "clipboard"), &DisplayServer::clipboard_set); ClassDB::bind_method(D_METHOD("clipboard_get"), &DisplayServer::clipboard_get); + ClassDB::bind_method(D_METHOD("clipboard_set_primary", "clipboard_primary"), &DisplayServer::clipboard_set_primary); + ClassDB::bind_method(D_METHOD("clipboard_get_primary"), &DisplayServer::clipboard_get_primary); ClassDB::bind_method(D_METHOD("get_screen_count"), &DisplayServer::get_screen_count); ClassDB::bind_method(D_METHOD("screen_get_position", "screen"), &DisplayServer::screen_get_position, DEFVAL(SCREEN_OF_MAIN_WINDOW)); diff --git a/servers/display_server.h b/servers/display_server.h index f411a72aa3..8af7946a1e 100644 --- a/servers/display_server.h +++ b/servers/display_server.h @@ -161,6 +161,8 @@ public: virtual void clipboard_set(const String &p_text); virtual String clipboard_get() const; + virtual void clipboard_set_primary(const String &p_text); + virtual String clipboard_get_primary() const; enum { SCREEN_OF_MAIN_WINDOW = -1