Преглед изворни кода

Merge pull request #54026 from ConteZero/primary_clipboard_linux_3.x

Rémi Verschelde пре 3 година
родитељ
комит
ee818e1631

+ 8 - 0
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;
 }

+ 3 - 0
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 @@ public:
 	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;

+ 4 - 0
doc/classes/LineEdit.xml

@@ -145,6 +145,10 @@
 			# `text_change_rejected` is emitted with "bye" as parameter.
 			[/codeblock]
 		</member>
+		<member name="middle_mouse_paste_enabled" type="bool" setter="set_middle_mouse_paste_enabled" getter="is_middle_mouse_paste_enabled" default="true">
+			If [code]false[/code], using middle mouse button to paste clipboard will be disabled.
+			[b]Note:[/b] This method is only implemented on Linux.
+		</member>
 		<member name="mouse_default_cursor_shape" type="int" setter="set_default_cursor_shape" getter="get_default_cursor_shape" overrides="Control" enum="Control.CursorShape" default="1" />
 		<member name="placeholder_alpha" type="float" setter="set_placeholder_alpha" getter="get_placeholder_alpha" default="0.6">
 			Opacity of the [member placeholder_text]. From [code]0[/code] to [code]1[/code].

+ 4 - 0
doc/classes/TextEdit.xml

@@ -514,6 +514,10 @@
 		<member name="highlight_current_line" type="bool" setter="set_highlight_current_line" getter="is_highlight_current_line_enabled" default="false">
 			If [code]true[/code], the line containing the cursor is highlighted.
 		</member>
+		<member name="middle_mouse_paste_enabled" type="bool" setter="set_middle_mouse_paste_enabled" getter="is_middle_mouse_paste_enabled" default="true">
+			If [code]false[/code], using middle mouse button to paste clipboard will be disabled.
+			[b]Note:[/b] This method is only implemented on Linux.
+		</member>
 		<member name="minimap_draw" type="bool" setter="draw_minimap" getter="is_drawing_minimap" default="false">
 			If [code]true[/code], a minimap is shown, providing an outline of your source code.
 		</member>

+ 1 - 0
main/main.cpp

@@ -2417,6 +2417,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();

+ 49 - 6
platform/x11/os_x11.cpp

@@ -2177,7 +2177,7 @@ void OS_X11::_handle_key_event(XKeyEvent *p_event, LocalVector<XEvent> &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 {

+ 3 - 1
platform/x11/os_x11.h

@@ -157,7 +157,7 @@ class OS_X11 : public OS_Unix {
 
 	void _handle_key_event(XKeyEvent *p_event, LocalVector<XEvent> &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 @@ public:
 
 	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();

+ 41 - 0
scene/gui/line_edit.cpp

@@ -67,6 +67,26 @@ void LineEdit::_gui_input(Ref<InputEvent> 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<InputEvent> 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<InputEvent> 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<InputEvent> 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;
 

+ 4 - 0
scene/gui/line_edit.h

@@ -95,6 +95,7 @@ private:
 
 	bool drag_action = false;
 	bool drag_caret_force_displayed = false;
+	bool middle_mouse_paste_enabled;
 
 	Ref<Texture> right_icon;
 
@@ -239,6 +240,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;
 

+ 7 - 0
scene/gui/rich_text_label.cpp

@@ -1205,6 +1205,9 @@ void RichTextLabel::_gui_input(Ref<InputEvent> 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<InputEvent> 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<InputEvent> p_event) {
 					swap = true;
 				} else if (selection.from_char == selection.to_char) {
 					selection.active = false;
+					update();
 					return;
 				}
 			}

+ 41 - 0
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<InputEvent> &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<InputEvent> &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.

+ 5 - 0
scene/gui/text_edit.h

@@ -444,6 +444,8 @@ private:
 
 	bool virtual_keyboard_enabled = true;
 
+	bool middle_mouse_paste_enabled;
+
 	int executing_line;
 
 	void _generate_context_menu();
@@ -864,6 +866,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;
+
 	PopupMenu *get_menu() const;
 
 	String get_text_for_completion();