Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.x] Added primary clipboard for Linux #54026

Merged
merged 1 commit into from
Mar 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions core/os/os.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
3 changes: 3 additions & 0 deletions core/os/os.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions doc/classes/LineEdit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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].
Expand Down
4 changes: 4 additions & 0 deletions doc/classes/TextEdit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
1 change: 1 addition & 0 deletions main/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
55 changes: 49 additions & 6 deletions platform/x11/os_x11.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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";
}
Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 3 additions & 1 deletion platform/x11/os_x11.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down
41 changes: 41 additions & 0 deletions scene/gui/line_edit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
Expand All @@ -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));
}
}
}

Expand All @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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;

Expand Down
4 changes: 4 additions & 0 deletions scene/gui/line_edit.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ class LineEdit : public Control {

bool drag_action = false;
bool drag_caret_force_displayed = false;
bool middle_mouse_paste_enabled;

Ref<Texture> right_icon;

Expand Down Expand Up @@ -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;

Expand Down
7 changes: 7 additions & 0 deletions scene/gui/rich_text_label.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
}
}
Expand Down
Loading