From 6be2f84db47462fb52b1ff6958e22b86287c8c7a Mon Sep 17 00:00:00 2001
From: John Knight <80524176+Tekisasu-JohnK@users.noreply.github.com>
Date: Mon, 8 Dec 2025 05:19:14 -0600
Subject: [PATCH] Add remote debug client status section in editor UI's top
mainbar
---
core/debugger/remote_debugger_peer.cpp | 7 +
core/debugger/remote_debugger_peer.h | 2 +
doc/classes/EditorSettings.xml | 3 +
editor/debugger/script_editor_debugger.cpp | 15 ++
editor/debugger/script_editor_debugger.h | 1 +
editor/editor_node.cpp | 132 ++++++++++++++++++
editor/editor_node.h | 7 +
editor/run/editor_run_native.cpp | 2 +-
editor/settings/editor_settings.cpp | 1 +
.../remote_debugger_peer_websocket.cpp | 7 +
.../remote_debugger_peer_websocket.h | 1 +
11 files changed, 177 insertions(+), 1 deletion(-)
diff --git a/core/debugger/remote_debugger_peer.cpp b/core/debugger/remote_debugger_peer.cpp
index 536a83f67c37..c5c3ff049837 100644
--- a/core/debugger/remote_debugger_peer.cpp
+++ b/core/debugger/remote_debugger_peer.cpp
@@ -38,6 +38,13 @@ bool RemoteDebuggerPeerTCP::is_peer_connected() {
return connected;
}
+String RemoteDebuggerPeerTCP::get_peer_host() {
+ if (tcp_client.is_valid() && tcp_client->get_status() == StreamPeerTCP::STATUS_CONNECTED) {
+ return String(tcp_client->get_connected_host());
+ }
+ return "";
+}
+
bool RemoteDebuggerPeerTCP::has_message() {
return in_queue.size() > 0;
}
diff --git a/core/debugger/remote_debugger_peer.h b/core/debugger/remote_debugger_peer.h
index de7621ba3310..97a3ca368e03 100644
--- a/core/debugger/remote_debugger_peer.h
+++ b/core/debugger/remote_debugger_peer.h
@@ -44,6 +44,7 @@ class RemoteDebuggerPeer : public RefCounted {
public:
virtual bool is_peer_connected() = 0;
+ virtual String get_peer_host() = 0;
virtual int get_max_message_size() const = 0;
virtual bool has_message() = 0;
virtual Error put_message(const Array &p_arr) = 0;
@@ -85,6 +86,7 @@ class RemoteDebuggerPeerTCP : public RemoteDebuggerPeer {
Error connect_to_host(const String &p_host, uint16_t p_port);
bool is_peer_connected() override;
+ String get_peer_host() override;
int get_max_message_size() const override;
bool has_message() override;
Error put_message(const Array &p_arr) override;
diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml
index ceaf66ef27a7..869db9bf9969 100644
--- a/doc/classes/EditorSettings.xml
+++ b/doc/classes/EditorSettings.xml
@@ -962,6 +962,9 @@
If enabled, displays internal engine errors in toast notifications (toggleable by clicking the "bell" icon at the bottom of the editor). No matter the value of this setting, non-internal engine errors will always be visible in toast notifications.
The default [b]Auto[/b] value will only enable this if the editor was compiled with the [code]dev_build=yes[/code] SCons option (the default is [code]dev_build=no[/code]).
+
+ If enabled, displays a new area in the top bar of the editor UI that shows the status of a remote debug session. Hovering over this area will show a tooltip of the ip address.
+
If enabled, displays an icon in the top-right corner of the editor that spins when the editor redraws a frame. This can be used to diagnose situations where the engine is constantly redrawing, which should be avoided as this increases CPU and GPU utilization for no good reason. To further troubleshoot these situations, start the editor with the [code]--debug-canvas-item-redraw[/code] [url=$DOCS_URL/tutorials/editor/command_line_tutorial.html]command line argument[/url].
Consider enabling this if you are developing editor plugins to ensure they only make the editor redraw when required.
diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp
index 69db65c1d683..9456517a547e 100644
--- a/editor/debugger/script_editor_debugger.cpp
+++ b/editor/debugger/script_editor_debugger.cpp
@@ -1379,6 +1379,21 @@ void ScriptEditorDebugger::_resources_reimported(const PackedStringArray &p_reso
_put_msg("scene:reload_cached_files", msg);
}
+String ScriptEditorDebugger::get_connected_host_ip() {
+ if (!peer.is_valid() || !peer->is_peer_connected()) {
+ return "";
+ }
+
+ // Try to cast to TCP peer to get the IP address
+ Ref tcp_peer = peer;
+ if (tcp_peer.is_valid()) {
+ return tcp_peer->get_peer_host();
+ }
+
+ // For non-TCP peers (e.g., WebSocket), return a generic message
+ return "Connected";
+}
+
int ScriptEditorDebugger::_get_node_path_cache(const NodePath &p_path) {
const int *r = node_path_cache.getptr(p_path);
if (r) {
diff --git a/editor/debugger/script_editor_debugger.h b/editor/debugger/script_editor_debugger.h
index 1c9216904313..8a6791581b13 100644
--- a/editor/debugger/script_editor_debugger.h
+++ b/editor/debugger/script_editor_debugger.h
@@ -330,6 +330,7 @@ class ScriptEditorDebugger : public MarginContainer {
void debug_continue();
bool is_breaked() const { return threads_debugged.size() > 0; }
bool is_debuggable() const { return threads_debugged.size() > 0 && threads_debugged[debugging_thread_id].can_debug; }
+ String get_connected_host_ip();
bool is_session_active() { return peer.is_valid() && peer->is_peer_connected(); }
int get_remote_pid() const { return remote_pid; }
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index f6646992a7b3..d50a38704c02 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -689,6 +689,16 @@ void EditorNode::_update_theme(bool p_skip_creation) {
help_menu->set_item_icon(help_menu->get_item_index(HELP_ABOUT), _get_editor_theme_native_menu_icon(SNAME("Godot"), global_menu, dark_mode));
help_menu->set_item_icon(help_menu->get_item_index(HELP_SUPPORT_GODOT_DEVELOPMENT), _get_editor_theme_native_menu_icon(SNAME("Heart"), global_menu, dark_mode));
+ // Initialize debug target status icon
+ if (debug_target_status && (bool)EDITOR_GET("interface/editor/show_remote_debug_connection_status") == true) {
+ Ref icon = theme->get_icon(SNAME("GuiSliderGrabber"), EditorStringName(EditorIcons));
+ debug_target_status->set_button_icon(icon);
+ debug_target_status->add_theme_color_override("icon_normal_color", Color(0.94, 0.44, 0.56, 1.0)); // Red is for disconnected
+ debug_target_status->add_theme_color_override("icon_pressed_color", Color(0.94, 0.44, 0.56, 1.0));
+ debug_target_status->add_theme_color_override("icon_hover_color", Color(0.94, 0.44, 0.56, 1.0));
+ debug_target_status->set_self_modulate(Color(1, 1, 1, 0.95)); // 95% opacity for icon and text
+ }
+
_update_renderer_color();
}
@@ -802,6 +812,9 @@ void EditorNode::_notification(int p_what) {
scene_tabs->update_scene_tabs();
}
+ // Update debug target status
+ _update_debug_target_status();
+
// Update the animation frame of the update spinner.
uint64_t frame = Engine::get_singleton()->get_frames_drawn();
uint64_t tick = OS::get_singleton()->get_ticks_msec();
@@ -7805,6 +7818,73 @@ HashMap EditorNode::get_initial_settings() {
return settings;
}
+void EditorNode::_update_debug_target_status() {
+ if (!debug_target_status) {
+ return;
+ }
+
+ // Get the current debugger
+ EditorDebuggerNode *debugger_node = EditorDebuggerNode::get_singleton();
+ if (!debugger_node) {
+ return;
+ }
+
+ ScriptEditorDebugger *debugger = debugger_node->get_default_debugger();
+ if (!debugger) {
+ return;
+ }
+
+ // Check if session is active
+ bool is_connected = debugger->is_session_active();
+
+ // Only update the ui if state has changed
+ if (is_connected != debug_target_last_connected_state) {
+ // Connection state changed, update everything
+ debug_target_last_connected_state = is_connected;
+
+ if (is_connected) {
+ // Connected state: green icon + stringified IP address
+ String ip_address = debugger->get_connected_host_ip();
+ if (ip_address.is_empty()) {
+ ip_address = "Connected";
+ }
+ debug_target_last_ip = ip_address;
+
+ Ref icon = theme->get_icon(SNAME("GuiSliderGrabber"), EditorStringName(EditorIcons));
+ debug_target_status->set_button_icon(icon);
+ debug_target_status->add_theme_color_override("icon_normal_color", Color(0.46, 0.85, 0.69, 1.0));
+ debug_target_status->add_theme_color_override("icon_pressed_color", Color(0.46, 0.85, 0.69, 1.0));
+ debug_target_status->add_theme_color_override("icon_hover_color", Color(0.46, 0.85, 0.69, 1.0));
+ debug_target_status->add_theme_color_override("font_color", Color(1, 1, 1, 0.95));
+ debug_target_status->set_tooltip_text(vformat("Connected to: %s", ip_address));
+ debug_target_label->set_tooltip_text(vformat("Connected to: %s", ip_address));
+ } else {
+ // Disconnected state: red icon + "No Connection"
+ debug_target_last_ip = "";
+
+ Ref icon = theme->get_icon(SNAME("GuiSliderGrabber"), EditorStringName(EditorIcons));
+ debug_target_status->set_button_icon(icon);
+ debug_target_status->add_theme_color_override("icon_normal_color", Color(0.94, 0.44, 0.56, 1.0));
+ debug_target_status->add_theme_color_override("icon_pressed_color", Color(0.94, 0.44, 0.56, 1.0));
+ debug_target_status->add_theme_color_override("icon_hover_color", Color(0.94, 0.44, 0.56, 1.0));
+ debug_target_status->add_theme_color_override("font_color", Color(1, 1, 1, 0.95));
+ debug_target_status->set_tooltip_text("No Connection");
+ debug_target_label->set_tooltip_text("No Connection");
+ }
+ } else if (is_connected) {
+ // Connected state hasn't changed, but IP might have
+ String ip_address = debugger->get_connected_host_ip();
+ if (ip_address.is_empty()) {
+ ip_address = "Connected";
+ }
+ if (ip_address != debug_target_last_ip) {
+ debug_target_last_ip = ip_address;
+ debug_target_status->set_tooltip_text(vformat("Connected to: %s", ip_address));
+ debug_target_label->set_tooltip_text(vformat("Connected to: %s", ip_address));
+ }
+ }
+}
+
EditorNode::EditorNode() {
DEV_ASSERT(!singleton);
singleton = this;
@@ -8585,6 +8665,58 @@ EditorNode::EditorNode() {
project_run_bar->connect("play_pressed", callable_mp(this, &EditorNode::_project_run_started));
project_run_bar->connect("stop_pressed", callable_mp(this, &EditorNode::_project_run_stopped));
+ if ((bool)EDITOR_GET("interface/editor/show_remote_debug_connection_status") == true) {
+ // Transparent non-interactive label spacer for debug target section (left side)
+ Label *debug_target_spacer_left = memnew(Label);
+ debug_target_spacer_left->set_text(" | ");
+ debug_target_spacer_left->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ debug_target_spacer_left->add_theme_color_override("font_color", Color(1, 1, 1, .2));
+ title_bar->add_child(debug_target_spacer_left);
+
+ // Debug target section
+ debug_target_hb = memnew(HBoxContainer);
+ title_bar->add_child(debug_target_hb);
+
+ // "Debug Client:" label
+ debug_target_label = memnew(Button);
+ debug_target_label->set_text("Debug Client:");
+ debug_target_label->set_flat(true);
+ debug_target_label->set_focus_mode(Control::FOCUS_NONE);
+ debug_target_label->set_mouse_filter(Control::MOUSE_FILTER_PASS);
+ debug_target_label->add_theme_color_override("font_color", Color(1, 1, 1, 0.5));
+ //debug_target_label->add_theme_font_size_override(SceneStringName(font_size), 11);
+ Ref label_empty_style;
+ label_empty_style.instantiate();
+ debug_target_label->set_tooltip_text("No Connection");
+ debug_target_label->add_theme_style_override("normal", label_empty_style);
+ debug_target_label->add_theme_style_override("hover", label_empty_style);
+ debug_target_label->add_theme_style_override("pressed", label_empty_style);
+ debug_target_label->add_theme_style_override("focus", label_empty_style);
+ debug_target_hb->add_child(debug_target_label);
+ // Connection status button
+ debug_target_status = memnew(Button);
+ debug_target_status->set_flat(true);
+ debug_target_status->set_focus_mode(Control::FOCUS_NONE);
+ debug_target_status->set_mouse_filter(Control::MOUSE_FILTER_PASS);
+ Ref status_empty_style;
+ status_empty_style.instantiate();
+ debug_target_status->add_theme_style_override("normal", status_empty_style);
+ debug_target_status->add_theme_style_override("hover", status_empty_style);
+ debug_target_status->add_theme_style_override("pressed", status_empty_style);
+ debug_target_status->add_theme_style_override("focus", status_empty_style);
+ // Set initial disconnected state
+ debug_target_status->set_tooltip_text("No Connection");
+ debug_target_status->add_theme_color_override("font_color", Color(1, 1, 1, 0.85));
+ debug_target_hb->add_child(debug_target_status);
+
+ // Transparent non-interactive label spacer for debug target section (right side)
+ Label *debug_target_spacer_right = memnew(Label);
+ debug_target_spacer_right->set_text(" | ");
+ debug_target_spacer_right->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ debug_target_spacer_right->add_theme_color_override("font_color", Color(1, 1, 1, .2));
+ title_bar->add_child(debug_target_spacer_right);
+ }
+
right_menu_hb = memnew(HBoxContainer);
right_menu_hb->set_mouse_filter(Control::MOUSE_FILTER_STOP);
title_bar->add_child(right_menu_hb);
diff --git a/editor/editor_node.h b/editor/editor_node.h
index d65d1f620103..355002757a66 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -331,6 +331,11 @@ class EditorNode : public Node {
Control *right_menu_spacer = nullptr;
EditorTitleBar *title_bar = nullptr;
EditorRunBar *project_run_bar = nullptr;
+ HBoxContainer *debug_target_hb = nullptr;
+ Button *debug_target_label = nullptr;
+ Button *debug_target_status = nullptr;
+ bool debug_target_last_connected_state = false;
+ String debug_target_last_ip = "";
HBoxContainer *right_menu_hb = nullptr;
// Spacers to center 2D / 3D / Script buttons.
@@ -720,6 +725,8 @@ class EditorNode : public Node {
void _update_main_menu_type();
void _add_to_main_menu(const String &p_name, PopupMenu *p_menu);
+ void _update_debug_target_status();
+
protected:
friend class FileSystemDock;
diff --git a/editor/run/editor_run_native.cpp b/editor/run/editor_run_native.cpp
index f4311cf3e9a9..bc10a7042241 100644
--- a/editor/run/editor_run_native.cpp
+++ b/editor/run/editor_run_native.cpp
@@ -59,7 +59,7 @@ void EditorRunNative::_notification(int p_what) {
const int device_count = MIN(eep->get_options_count(), 9000);
String error;
if (device_count > 0 && preset->is_runnable()) {
- popup->add_icon_item(eep->get_run_icon(), eep->get_name(), -1);
+ popup->add_icon_item(eep->get_run_icon(), preset->get_name(), -1);
popup->set_item_disabled(-1, true);
for (int j = 0; j < device_count; j++) {
popup->add_icon_item(eep->get_option_icon(j), eep->get_option_label(j), EditorExport::encode_platform_device_id(platform_idx, j));
diff --git a/editor/settings/editor_settings.cpp b/editor/settings/editor_settings.cpp
index e25c3d29cb8b..3c7bd692a1c8 100644
--- a/editor/settings/editor_settings.cpp
+++ b/editor/settings/editor_settings.cpp
@@ -517,6 +517,7 @@ void EditorSettings::_load_defaults(Ref p_extra_config) {
EDITOR_SETTING_USAGE(Variant::INT, PROPERTY_HINT_ENUM, "interface/editor/accept_dialog_cancel_ok_buttons", 0,
vformat("Auto (%s),Cancel First,OK First", DisplayServer::get_singleton()->get_swap_cancel_ok() ? "OK First" : "Cancel First"),
PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED);
+ EDITOR_SETTING_USAGE(Variant::BOOL, PROPERTY_HINT_NONE, "interface/editor/show_remote_debug_connection_status", false, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
#ifdef DEV_ENABLED
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "interface/editor/show_internal_errors_in_toast_notifications", 0, "Auto (Enabled),Enabled,Disabled")
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "interface/editor/show_update_spinner", 0, "Auto (Enabled),Enabled,Disabled")
diff --git a/modules/websocket/remote_debugger_peer_websocket.cpp b/modules/websocket/remote_debugger_peer_websocket.cpp
index 5102f402b9a7..3a2ede1f273f 100644
--- a/modules/websocket/remote_debugger_peer_websocket.cpp
+++ b/modules/websocket/remote_debugger_peer_websocket.cpp
@@ -61,6 +61,13 @@ bool RemoteDebuggerPeerWebSocket::is_peer_connected() {
return ws_peer.is_valid() && (ws_peer->get_ready_state() == WebSocketPeer::STATE_OPEN || ws_peer->get_ready_state() == WebSocketPeer::STATE_CONNECTING);
}
+String RemoteDebuggerPeerWebSocket::get_peer_host() {
+ if (ws_peer.is_valid() && ws_peer->get_ready_state() == WebSocketPeer::STATE_OPEN) {
+ return String(ws_peer->get_connected_host());
+ }
+ return "";
+}
+
void RemoteDebuggerPeerWebSocket::poll() {
ERR_FAIL_COND(ws_peer.is_null());
ws_peer->poll();
diff --git a/modules/websocket/remote_debugger_peer_websocket.h b/modules/websocket/remote_debugger_peer_websocket.h
index 149fa6973963..20c7b5febe2b 100644
--- a/modules/websocket/remote_debugger_peer_websocket.h
+++ b/modules/websocket/remote_debugger_peer_websocket.h
@@ -49,6 +49,7 @@ class RemoteDebuggerPeerWebSocket : public RemoteDebuggerPeer {
Error connect_to_host(const String &p_uri);
bool is_peer_connected() override;
+ String get_peer_host() override;
int get_max_message_size() const override;
bool has_message() override;
Error put_message(const Array &p_arr) override;