Skip to content

Add Commands Window for running ADB and shell commands#213

Open
DiannaUnity wants to merge 3 commits into
masterfrom
features/commandWindow
Open

Add Commands Window for running ADB and shell commands#213
DiannaUnity wants to merge 3 commits into
masterfrom
features/commandWindow

Conversation

@DiannaUnity

@DiannaUnity DiannaUnity commented May 14, 2026

Copy link
Copy Markdown
Collaborator

Purpose of PR

Type of change:

  • Bug fix
  • New feature
  • Code refactor
  • Breaking change
  • Documentation update
  • Other (please describe)

Description of the feature
Adds a new Commands window to the Android Logcat package, accessible via Tools > Commands in the logcat console toolbar. It provides a way to build, organize, and execute ADB and shell commands directly from the Unity Editor without needing an external terminal.

  • Favorites / General command lists with reorder, favorite/unfavorite, edit, and delete support (toggled via Edit Mode)
  • Built-in catalog of ~100 pre-defined ADB commands covering device management, app lifecycle, logcat, screen capture, system info, input simulation, networking, dumpsys, Meta Quest/XR, and bundletool — browsable via a Search Catalog window with quick-filter chips
  • Add Command dialog supporting multi-line commands that execute sequentially
  • Placeholder resolution — commands containing tokens (e.g. , ) prompt the user to fill in values before execution, with smart defaults (e.g. auto-populates from PlayerSettings.applicationIdentifier)
  • Import / Export commands as JSON for sharing across teams
  • Inline output panel with a resizable splitter, plus a dedicated pop-out Command Output window with copy-all and auto-scroll
  • Automatically injects -s for the currently selected device on ADB commands
  • Commands persist in user settings across sessions

Halo effect

  • The Commands window is self-contained and isolated from the main logcat console. The only integration points are a new Commands entry in the ToolsContextMenu enum, a menu item in the console toolbar, and a new CommandsSettingsData block in AndroidLogcatUserSettings for persistence.
    • No existing behavior or data flow is modified — the changes to existing files are purely additive (enum value, menu entry, settings field + initialization).
    • Low risk of breaking existing functionality.

Performance Impact

  • No expected performance impact. The Commands window is only instantiated when explicitly opened by the user. Command execution is synchronous and on-demand. The output buffer is capped at 3,000 lines (inline) / 5,000 lines (pop-out) to prevent unbounded memory growth.

Package requirements

  • Requires the Android platform module to be installed (same as the existing logcat package). No additional dependency packages needed.

Checklist for PR maker

  • Have you added a backport label? (if needed)
  • Have you updated the Changelog? Each package has a CHANGELOG.md file
  • Have you added or updated the Documentation to your PR? When you add a new feature, change a property name, or change the behaviour of a feature, it's best practice to include related documentation changes in the same PR

Testing status

No new automation tests added

Put any relevant Yamato links here
N/A

Testing done

  • Opened the Commands window from the logcat console Tools menu
  • Verified default commands populate on first launch
  • Added, edited, reordered, favorited, and deleted commands via Edit Mode
  • Ran ADB commands against a connected device and verified output appears inline and in the pop-out window
  • Tested placeholder resolution dialog with auto-fill from PlayerSettings
  • Tested Search Catalog window with quick-filter chips and adding catalog entries
  • Exported commands to JSON and re-imported them
  • Verified commands persist across Editor sessions via user settings
  • Confirmed the window handles the "no Android module installed" case gracefully
  • What environment did you test on?
    macOS, Unity Editor 6000.6.0a2_64de5ee36373

  • Projects
    N/A

  • Testing checklist

  • Built and run editor Locally or Katana
  • Built a player Android/iOS (if applicable)
  • Run on device Android/iOS (if applicable)
  • If new feature has UI, does it match Unity style? (Unity HIG?)
  • Tested Undo/Redo + Prefab overrides + Alignment in Preset
  • All items have tooltips?

Note to guardian

N/A

Comments to reviewers

This is a self-contained new feature — a Commands window for running ADB and shell commands from the Editor. The changes to existing files are minimal and purely additive (a menu entry, an enum value, and a settings block for persistence). No existing behavior was modified. The bulk of the PR is the new window and its supporting dialogs/utilities.

dockable command window pop-out
image

Create adb commands with multiple functions
image

Searching a catalog of 100+ adb commands
image

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new “Commands” EditorWindow to the Android Logcat package, enabling users to build/manage and execute ADB/shell commands directly from the Unity Editor, with persistence via AndroidLogcatUserSettings.

Changes:

  • Added Commands window UI (favorites + general lists, edit mode operations, import/export, inline output panel).
  • Added supporting UI/components: placeholder resolution dialog, command search window, built-in ADB command catalog, and a command output pop-out window.
  • Integrated the window into the existing Logcat console Tools menu and persisted data in user settings.

Reviewed changes

Copilot reviewed 10 out of 17 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
com.unity.mobile.android-logcat/Editor/AndroidLogcatUserSettings.cs Adds persisted CommandsSettingsData for favorites/general command lists.
com.unity.mobile.android-logcat/Editor/AndroidLogcatPlaceholderDialog.cs New dialog to resolve <placeholder> tokens before execution.
com.unity.mobile.android-logcat/Editor/AndroidLogcatPlaceholderDialog.cs.meta Meta for new placeholder dialog asset.
com.unity.mobile.android-logcat/Editor/AndroidLogcatContextMenu.cs Adds ToolsContextMenu.Commands enum entry.
com.unity.mobile.android-logcat/Editor/AndroidLogcatConsoleWindow.cs Hooks the new Commands window into the Tools menu.
com.unity.mobile.android-logcat/Editor/AndroidLogcatCommandsWindow.cs Main Commands window implementation (lists, execution, import/export, output).
com.unity.mobile.android-logcat/Editor/AndroidLogcatCommandsWindow.cs.meta Meta for new commands window asset.
com.unity.mobile.android-logcat/Editor/AndroidLogcatCommandSearchWindow.cs Catalog + local search window for adding/running commands.
com.unity.mobile.android-logcat/Editor/AndroidLogcatCommandSearchWindow.cs.meta Meta for new search window asset.
com.unity.mobile.android-logcat/Editor/AndroidLogcatCommandOutputWindow.cs Dedicated output window (copy-all, auto-scroll).
com.unity.mobile.android-logcat/Editor/AndroidLogcatCommandOutputWindow.cs.meta Meta for new output window asset.
com.unity.mobile.android-logcat/Editor/AndroidLogcatCommandEntry.cs Defines command entry model + import/export DTOs/helpers.
com.unity.mobile.android-logcat/Editor/AndroidLogcatCommandEntry.cs.meta Meta for new command entry asset.
com.unity.mobile.android-logcat/Editor/AndroidLogcatAddCommandDialog.cs New dialog for adding/editing multi-line commands.
com.unity.mobile.android-logcat/Editor/AndroidLogcatAddCommandDialog.cs.meta Meta for new add/edit dialog asset.
com.unity.mobile.android-logcat/Editor/AndroidLogcatAdbCommandCatalog.cs New built-in catalog of predefined ADB commands.
com.unity.mobile.android-logcat/Editor/AndroidLogcatAdbCommandCatalog.cs.meta Meta for new catalog asset.
Files not reviewed (7)
  • com.unity.mobile.android-logcat/Editor/AndroidLogcatAdbCommandCatalog.cs.meta: Language not supported
  • com.unity.mobile.android-logcat/Editor/AndroidLogcatAddCommandDialog.cs.meta: Language not supported
  • com.unity.mobile.android-logcat/Editor/AndroidLogcatCommandEntry.cs.meta: Language not supported
  • com.unity.mobile.android-logcat/Editor/AndroidLogcatCommandOutputWindow.cs.meta: Language not supported
  • com.unity.mobile.android-logcat/Editor/AndroidLogcatCommandSearchWindow.cs.meta: Language not supported
  • com.unity.mobile.android-logcat/Editor/AndroidLogcatCommandsWindow.cs.meta: Language not supported
  • com.unity.mobile.android-logcat/Editor/AndroidLogcatPlaceholderDialog.cs.meta: Language not supported
Comments suppressed due to low confidence (8)

com.unity.mobile.android-logcat/Editor/AndroidLogcatCommandsWindow.cs:196

  • Row click selection is checking GUILayoutUtility.GetLastRect() immediately after BeginHorizontal, before any controls in this row have been laid out. This rect will refer to the previously drawn control/row and can make selection unreliable. Capture the row rect explicitly (e.g., using GUILayoutUtility.GetRect / a selectable label/button spanning the row) and test Event.current.mousePosition against that rect instead.
            EditorGUILayout.BeginHorizontal(EditorStyles.helpBox, GUILayout.Height(kRowHeight));

            if (isSelected)
                GUI.backgroundColor = Color.white;

            // Click to select
            if (Event.current.type == EventType.MouseDown && GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition))
            {
                selectedIndex = index;
                Repaint();
            }

com.unity.mobile.android-logcat/Editor/AndroidLogcatCommandsWindow.cs:307

  • The splitter drag logic uses GUIUtility.hotControl != 0 as the only guard. hotControl can be non-zero for other controls (text fields, scroll views, etc.), so dragging elsewhere in the window can inadvertently resize the output area. Track a dedicated splitter control ID (store it on MouseDown) or a boolean like m_DraggingSplitter and only resize while that specific control is active.
        void DoOutputSplitter(float y)
        {
            var splitterRect = new Rect(0, y, position.width, 4);
            EditorGUIUtility.AddCursorRect(splitterRect, MouseCursor.ResizeVertical);

            if (Event.current.type == EventType.MouseDown && splitterRect.Contains(Event.current.mousePosition))
                GUIUtility.hotControl = GUIUtility.GetControlID(FocusType.Passive);

            if (GUIUtility.hotControl != 0 && Event.current.type == EventType.MouseDrag)
            {
                m_OutputHeight = Mathf.Clamp(position.height - Event.current.mousePosition.y, 50, position.height - 150);
                Repaint();
            }

            if (Event.current.type == EventType.MouseUp)
                GUIUtility.hotControl = 0;

com.unity.mobile.android-logcat/Editor/AndroidLogcatCommandsWindow.cs:331

  • A new GUIStyle is allocated for every output line on every OnGUI repaint. With up to 3000 lines this will generate a lot of GC pressure and can make the window sluggish. Cache the style once (e.g., a field initialized lazily) and only update the color via GUIStyleState or use GUI.contentColor/EditorGUI.DrawRect approaches that avoid per-line allocations.
            for (int i = 0; i < m_OutputLines.Count; i++)
            {
                var line = m_OutputLines[i];
                var style = new GUIStyle(EditorStyles.label)
                {
                    wordWrap = true,
                    richText = false
                };
                style.normal.textColor = line.color;
                EditorGUILayout.LabelField(line.text, style);
            }

com.unity.mobile.android-logcat/Editor/AndroidLogcatCommandsWindow.cs:212

  • A new GUIStyle is allocated for each command row (cmdStyle) on every repaint. With large command lists this can create avoidable GC churn. Consider caching this style (or using a shared static style) instead of constructing it in the row loop.
            var cmdStyle = new GUIStyle(EditorStyles.label);
            cmdStyle.normal.textColor = kCommandColor;
            EditorGUILayout.LabelField(cmdDisplay, cmdStyle);

com.unity.mobile.android-logcat/Editor/AndroidLogcatCommandsWindow.cs:396

  • Command execution is fully synchronous on the UI thread (ADB.Run / Shell.RunProcess inside the OnGUI button path). Commands like adb logcat, large dumpsys, network hangs, etc. can freeze the Unity Editor until completion. Consider running commands asynchronously (background process with incremental output + cancellation) or at least warning/guarding against known long-running commands.
        void ExecuteCommand(string resolvedCommand)
        {
            var lines = resolvedCommand.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);

            m_Running = true;
            Repaint();

            var device = m_DeviceSelection != null ? m_DeviceSelection.SelectedDevice : null;
            var deviceId = device != null ? device.Id : null;

            foreach (var rawLine in lines)
            {
                var cmd = rawLine.Trim();
                if (string.IsNullOrEmpty(cmd))
                    continue;

                AppendOutput($"> {cmd}", kAccentColor);

                try
                {
                    string result;
                    if (cmd.StartsWith("adb ", StringComparison.OrdinalIgnoreCase))
                    {
                        var adbArgs = cmd.Substring(4);

                        // Inject device serial if we have a selected device and the command doesn't already specify -s
                        if (!string.IsNullOrEmpty(deviceId) && !adbArgs.TrimStart().StartsWith("-s "))
                            adbArgs = $"-s {deviceId} {adbArgs}";

                        result = m_Runtime.Tools.ADB.Run(new[] { adbArgs }, "");
                    }
                    else
                    {
                        var parts = cmd.Split(new[] { ' ' }, 2);
                        var fileName = parts[0];
                        var arguments = parts.Length > 1 ? parts[1] : "";
                        var shellResult = Shell.RunProcess(fileName, arguments);
                        result = shellResult.GetStandardOut();
                        var err = shellResult.GetStandardErr();
                        if (!string.IsNullOrEmpty(err))
                            result = string.IsNullOrEmpty(result) ? err : result + "\n" + err;
                    }

                    AppendOutput(result, GUI.skin.label.normal.textColor);
                }
                catch (Exception ex)
                {
                    AppendOutput($"[Error] {ex.Message}", kErrorColor);
                }
            }

            m_Running = false;
            Repaint();
        }

com.unity.mobile.android-logcat/Editor/AndroidLogcatCommandsWindow.cs:380

  • Non-ADB command parsing splits on the first space to get the executable name (parts[0]). This breaks common cases like quoted executables/paths with spaces (e.g. "C:\Program Files...\tool.exe" args). Consider using a proper command-line parser that respects quotes/escaping (or restrict the feature to adb/shell explicitly and validate inputs).
                        var parts = cmd.Split(new[] { ' ' }, 2);
                        var fileName = parts[0];
                        var arguments = parts.Length > 1 ? parts[1] : "";
                        var shellResult = Shell.RunProcess(fileName, arguments);
                        result = shellResult.GetStandardOut();

com.unity.mobile.android-logcat/Editor/AndroidLogcatCommandsWindow.cs:405

  • Output lines are split only on '\n' when appending, so Windows CRLF output will leave a trailing '\r' character in each line (rendering as a stray character in the UI). Consider trimming '\r' per line or splitting on "\r\n"/"\n" robustly.
            foreach (var line in text.Split('\n'))
                m_OutputLines.Add(new OutputLine { text = line, color = color });

com.unity.mobile.android-logcat/Editor/AndroidLogcatCommandsWindow.cs:497

  • Imported JSON commands are accepted without validation/sanitization. If an entry has null/empty name or command (or the array contains null elements), later code paths like entry.command.IndexOfAny(...) and MatchesSearch(...) will throw NullReferenceException and break the window. Consider filtering out null entries and validating required fields (name/command) during import (and/or when loading from user settings).
            AndroidLogcatCommandExportData data;
            try
            {
                var json = File.ReadAllText(path);
                data = JsonUtility.FromJson<AndroidLogcatCommandExportData>(json);
            }
            catch (Exception ex)
            {
                EditorUtility.DisplayDialog("Import Failed",
                    $"Could not parse the selected file:\n{ex.Message}", "OK");
                return;
            }

            if ((data.favorites == null || data.favorites.Length == 0) &&
                (data.general == null || data.general.Length == 0))
            {
                EditorUtility.DisplayDialog("Import Failed",
                    "The file contains no commands.", "OK");
                return;
            }

            if (!EditorUtility.DisplayDialog("Import Commands",
                "This will replace all your current commands. Continue?", "Import", "Cancel"))
                return;

            m_Favorites.Clear();
            if (data.favorites != null)
                m_Favorites.AddRange(data.favorites);

            m_GeneralCommands.Clear();
            if (data.general != null)
                m_GeneralCommands.AddRange(data.general);

            SaveCommands();
            Debug.Log($"[Android Logcat] Commands imported from {path}");
        }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +80 to +84
var resolved = m_OriginalCommand;
foreach (var placeholder in m_Placeholders)
{
resolved = resolved.Replace($"<{placeholder.token}>", placeholder.value);
}
Comment on lines +115 to +136
if (GUILayout.Button("Add Command", EditorStyles.toolbarButton))
OnAddCommand();

var editLabel = m_EditMode ? "Edit Mode: ON" : "Edit Mode: OFF";
if (GUILayout.Button(editLabel, EditorStyles.toolbarButton))
m_EditMode = !m_EditMode;

if (GUILayout.Button("Import", EditorStyles.toolbarButton))
OnImportCommands();

if (GUILayout.Button("Export", EditorStyles.toolbarButton))
OnExportCommands();

if (GUILayout.Button("Search Catalog", EditorStyles.toolbarButton))
OpenSearchWindow();

GUILayout.FlexibleSpace();

if (m_Running)
GUILayout.Label("Running...", EditorStyles.miniLabel);

EditorGUILayout.EndHorizontal();

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented in commit 1c361bb: added an explicit Open Output button in the Commands top toolbar that calls AndroidLogcatCommandOutputWindow.Open(m_OutputLines), so the pop-out output window is directly reachable.

Screenshot: Commands window toolbar now includes the new Open Output action next to Search Catalog.

Comment on lines +74 to +84
for (int i = 0; i < m_Lines.Count; i++)
{
var line = m_Lines[i];
var style = new GUIStyle(EditorStyles.label)
{
wordWrap = true,
richText = false
};
style.normal.textColor = line.color;
EditorGUILayout.LabelField(line.text, style);
}
Comment on lines +11 to +51
static readonly Regex s_PlaceholderRegex = new Regex(@"<([^>]+)>", RegexOptions.Compiled);

string m_OriginalCommand;
Action<string> m_OnConfirm;
List<PlaceholderInfo> m_Placeholders = new List<PlaceholderInfo>();

class PlaceholderInfo
{
internal string token;
internal string value;
}

internal static void Show(string command, Action<string> onConfirm)
{
var matches = s_PlaceholderRegex.Matches(command);
if (matches.Count == 0)
{
onConfirm?.Invoke(command);
return;
}

var wnd = CreateInstance<AndroidLogcatPlaceholderDialog>();
wnd.titleContent = new GUIContent("Resolve Placeholders");
wnd.m_OriginalCommand = command;
wnd.m_OnConfirm = onConfirm;
wnd.minSize = new Vector2(400, 150);
wnd.maxSize = new Vector2(600, 400);

var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (Match match in matches)
{
var token = match.Groups[1].Value;
if (!seen.Add(token))
continue;

var defaultValue = "";
if (token.Equals("package", StringComparison.OrdinalIgnoreCase))
defaultValue = PlayerSettings.applicationIdentifier;

wnd.m_Placeholders.Add(new PlaceholderInfo { token = token, value = defaultValue });
}

@u-pr u-pr Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May require changes

The review identified critical concerns regarding the execution of ADB commands on the Main Thread, which will lead to Editor freezes and broken UI feedback. Additionally, there are potential lifecycle issues that could cause NullReferenceException when child windows outlive their parent. Several performance optimizations regarding memory allocation and list manipulation are also recommended to ensure a smooth user experience.

🤖 Helpful? 👍/👎

m_Runtime.Closing -= OnDisable;
m_DeviceSelection.Dispose();
m_DeviceSelection = null;
m_Runtime = null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

When this window is closed, m_Runtime and m_DeviceSelection are set to null. However, spawned child windows like AndroidLogcatCommandSearchWindow, AndroidLogcatAddCommandDialog, and AndroidLogcatPlaceholderDialog can remain open. If a user interacts with them after closing this window (e.g., clicking 'Add', 'Save', or 'Run'), their callbacks will execute on this disposed context, leading to a NullReferenceException inside SaveCommands() or ExecuteCommand().

Consider either closing these child windows inside OnDisable or adding null checks (if (m_Runtime == null) return;) inside the passed callbacks.

🤖 Helpful? 👍/👎

{
var lines = resolvedCommand.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);

m_Running = true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The execution of shell and ADB commands here is synchronous and blocks the Unity Main Thread. This causes the Editor UI to completely freeze until the command finishes (and for commands like adb wait-for-device, it could freeze indefinitely).

As a consequence, the m_Running = true; Repaint(); state change never has a chance to render, meaning the "Running..." UI feedback will never be visible to the user. Have you considered using a background thread (e.g., via the existing AndroidLogcatDispatcher) to execute the commands without blocking the Editor?

📚 Additional Context
  • com.unity.mobile.android-logcat/Editor/AndroidTools/Shell.cs
  • com.unity.mobile.android-logcat/Editor/AndroidTools/AndroidBridge.cs
  • com.unity.mobile.android-logcat/Editor/Tasks/AndroidLogcatDispatcher.cs

🤖 Helpful? 👍/👎

public AndroidLogcatCommandEntry[] general = Array.Empty<AndroidLogcatCommandEntry>();
}

internal static class AndroidLogcatCommandJsonHelper

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

AndroidLogcatCommandJsonHelper and the AndroidLogcatCommandEntryList class above appear to be unused in this PR. Consider removing them to eliminate dead code.

🤖 Helpful? 👍/👎

s_Instance.m_Lines.Add(new OutputLine { text = line, color = color });
}

while (s_Instance.m_Lines.Count > kMaxLines)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using RemoveAt(0) in a while loop requires shifting all remaining elements in the underlying array on every iteration, leading to O(N * M) performance. This can cause noticeable UI lag when appending large command outputs. Consider using RemoveRange instead to drop the oldest lines in a single operation.

Suggested change (apply manually):

            if (s_Instance.m_Lines.Count > kMaxLines)
                s_Instance.m_Lines.RemoveRange(0, s_Instance.m_Lines.Count - kMaxLines);

🤖 Helpful? 👍/👎


// Command preview
var cmdDisplay = entry.command;
if (cmdDisplay.IndexOfAny(new[] { '\r', '\n' }) >= 0)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

Allocating new arrays (new[] { '\r', '\n' }) directly inside DoCommandRow creates unnecessary garbage collection pressure, as this runs repeatedly during OnGUI events (layout, repaint, etc.). Consider extracting this array to a static readonly char[].

🤖 Helpful? 👍/👎

foreach (var line in text.Split('\n'))
m_OutputLines.Add(new OutputLine { text = line, color = color });

while (m_OutputLines.Count > kMaxOutputLines)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to the command output window, repeatedly calling RemoveAt(0) requires shifting the remaining list elements on every iteration. Using RemoveRange is significantly more efficient.

Suggested change (apply manually):

            if (m_OutputLines.Count > kMaxOutputLines)
                m_OutputLines.RemoveRange(0, m_OutputLines.Count - kMaxOutputLines);

🤖 Helpful? 👍/👎

localResults.Add((e, "General"));

// Collect catalog results (excluding already-saved commands)
var savedCommands = new HashSet<string>(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Creating a HashSet and evaluating LINQ queries (Concat, Select) inside OnGUI will execute on every UI event (repaint, mouse move, etc.) whenever a search query is active. This can cause significant garbage collection pressure and frame drops. Consider caching savedCommands and catalogResults, updating them only when the search query or command lists actually change.

🤖 Helpful? 👍/👎

@AlexBedardReidU3D AlexBedardReidU3D left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of the included PR Bot suggestions are good ones, and should be considered.

If you can include screenshots of the new UI windows, and menus into the PR description, that would be very helpful

@todi1856 todi1856 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think during discussion we agreed this will be implemented with UIToolkit? But I see lots of IMGUI code, is that expected?

The problem is - sooner or later we'll need to rewrite logcat to use UIToolkit, thus it wouldn't be good to add more IMGUI code :)

@DiannaUnity

Copy link
Copy Markdown
Collaborator Author

I think during discussion we agreed this will be implemented with UIToolkit? But I see lots of IMGUI code, is that expected?

The problem is - sooner or later we'll need to rewrite logcat to use UIToolkit, thus it wouldn't be good to add more IMGUI code :)

Ah, i mixed up which UI we were going to, and kept it consistent with the current, let me address that and the screenshots, thank you!

@IGuscin

IGuscin commented May 18, 2026

Copy link
Copy Markdown

I had no chance to try it out yet, but reading through the changes raised a question:
Shouldn't we append existing functionality, rather than providing duplicate ways of doing the same?
As an example - all the logcat adb commands - these are the core of the package already.
Or screencapture, input simulation - these are available as well.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 27 changed files in this pull request and generated 8 comments.

Files not reviewed (12)
  • com.unity.mobile.android-logcat/Editor/AndroidLogcatAdbCommandCatalog.cs.meta: Language not supported
  • com.unity.mobile.android-logcat/Editor/AndroidLogcatAddCommandDialog.cs.meta: Language not supported
  • com.unity.mobile.android-logcat/Editor/AndroidLogcatCommandEntry.cs.meta: Language not supported
  • com.unity.mobile.android-logcat/Editor/AndroidLogcatCommandOutputWindow.cs.meta: Language not supported
  • com.unity.mobile.android-logcat/Editor/AndroidLogcatCommandSearchWindow.cs.meta: Language not supported
  • com.unity.mobile.android-logcat/Editor/AndroidLogcatCommandsWindow.cs.meta: Language not supported
  • com.unity.mobile.android-logcat/Editor/AndroidLogcatPlaceholderDialog.cs.meta: Language not supported
  • com.unity.mobile.android-logcat/Editor/UI/Layouts/AndroidLogcatAddCommand.uxml.meta: Language not supported
  • com.unity.mobile.android-logcat/Editor/UI/Layouts/AndroidLogcatCommandOutput.uxml.meta: Language not supported
  • com.unity.mobile.android-logcat/Editor/UI/Layouts/AndroidLogcatCommandSearch.uxml.meta: Language not supported
  • com.unity.mobile.android-logcat/Editor/UI/Layouts/AndroidLogcatCommands.uxml.meta: Language not supported
  • com.unity.mobile.android-logcat/Editor/UI/Layouts/AndroidLogcatPlaceholder.uxml.meta: Language not supported

Comment on lines +60 to +61
if (!AndroidBridge.AndroidExtensionsInstalled)
return;
Comment on lines +336 to +378
if (m_RunningLabel != null)
m_RunningLabel.style.display = DisplayStyle.Flex;

var deviceId = m_DeviceSelection?.SelectedDevice?.Id;

foreach (var rawLine in lines)
{
var cmd = rawLine.Trim();
if (string.IsNullOrEmpty(cmd))
continue;

AppendOutput($"> {cmd}", kAccentColor);

try
{
string result;
if (cmd.StartsWith("adb ", StringComparison.OrdinalIgnoreCase))
{
var adbArgs = cmd.Substring(4);
if (!string.IsNullOrEmpty(deviceId) && !adbArgs.TrimStart().StartsWith("-s "))
adbArgs = $"-s {deviceId} {adbArgs}";
result = m_Runtime.Tools.ADB.Run(new[] { adbArgs }, "");
}
else
{
var parts = cmd.Split(new[] { ' ' }, 2);
var shellResult = Shell.RunProcess(parts[0], parts.Length > 1 ? parts[1] : "");
result = shellResult.GetStandardOut();
var err = shellResult.GetStandardErr();
if (!string.IsNullOrEmpty(err))
result = string.IsNullOrEmpty(result) ? err : result + "\n" + err;
}

AppendOutput(result, GUI.skin.label.normal.textColor);
}
catch (Exception ex)
{
AppendOutput($"[Error] {ex.Message}", kErrorColor);
}
}

if (m_RunningLabel != null)
m_RunningLabel.style.display = DisplayStyle.None;
Comment on lines +291 to +306
void AppendOutput(string text, Color color)
{
if (string.IsNullOrEmpty(text))
return;

foreach (var line in text.Split('\n'))
{
m_OutputLines.Add(new OutputLine { text = line, color = color });
if (m_OutputContainer != null)
{
var label = new Label(line);
label.style.color = new StyleColor(color);
label.style.whiteSpace = WhiteSpace.Normal;
m_OutputContainer.Add(label);
}
}
Comment on lines +361 to +362
var parts = cmd.Split(new[] { ' ' }, 2);
var shellResult = Shell.RunProcess(parts[0], parts.Length > 1 ? parts[1] : "");
Comment on lines +440 to +453
if ((data.favorites == null || data.favorites.Length == 0) && (data.general == null || data.general.Length == 0))
{
EditorUtility.DisplayDialog("Import Failed", "The file contains no commands.", "OK");
return;
}

if (!EditorUtility.DisplayDialog("Import Commands", "This will replace all your current commands. Continue?", "Import", "Cancel"))
return;

m_Favorites.Clear();
if (data.favorites != null) m_Favorites.AddRange(data.favorites);
m_GeneralCommands.Clear();
if (data.general != null) m_GeneralCommands.AddRange(data.general);

Comment on lines +33 to +43
internal static void AppendIfOpen(string text, Color color)
{
if (s_Instance == null || string.IsNullOrEmpty(text))
return;

foreach (var line in text.Split('\n'))
{
var ol = new AndroidLogcatCommandsWindow.OutputLine { text = line, color = color };
s_Instance.m_Lines.Add(ol);
s_Instance.AddLineElement(ol);
}
Comment on lines +173 to +174
return entry.name.ToLowerInvariant().Contains(terms)
|| entry.command.ToLowerInvariant().Contains(terms);
Comment on lines +12 to +55
static readonly Regex s_PlaceholderRegex = new Regex(@"<([^>]+)>", RegexOptions.Compiled);

string m_OriginalCommand;
Action<string> m_OnConfirm;
List<PlaceholderInfo> m_Placeholders = new List<PlaceholderInfo>();

class PlaceholderInfo
{
internal string token;
internal string value;
}

internal static void Show(string command, Action<string> onConfirm)
{
var matches = s_PlaceholderRegex.Matches(command);
if (matches.Count == 0)
{
onConfirm?.Invoke(command);
return;
}

var wnd = CreateInstance<AndroidLogcatPlaceholderDialog>();
wnd.titleContent = new GUIContent("Resolve Placeholders");
wnd.m_OriginalCommand = command;
wnd.m_OnConfirm = onConfirm;
wnd.minSize = new Vector2(400, 150);
wnd.maxSize = new Vector2(600, 400);

var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (Match match in matches)
{
var token = match.Groups[1].Value;
if (!seen.Add(token))
continue;

var defaultValue = "";
if (token.Equals("package", StringComparison.OrdinalIgnoreCase))
defaultValue = PlayerSettings.applicationIdentifier;

wnd.m_Placeholders.Add(new PlaceholderInfo { token = token, value = defaultValue });
}

wnd.ShowUtility();
}

Copilot AI commented May 18, 2026

Copy link
Copy Markdown

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • artifactory.prd.it.unity3d.com
    • Triggering command: /usr/bin/dotnet dotnet test Tools/CI/UnityMobileLogcat-recipes.sln (dns block)
    • Triggering command: /opt/hostedtoolcache/CodeQL/2.25.1/x64/codeql/csharp/tools/linux64/Semmle.Autobuild.CSharp /opt/hostedtoolcache/CodeQL/2.25.1/x64/codeql/csharp/tools/linux64/Semmle.Autobuild.CSharp (dns block)
    • Triggering command: /usr/bin/dotnet dotnet restore --no-dependencies /home/REDACTED/work/com.unity.mobile.logcat/com.unity.mobile.logcat/Tools/CI/UnityMobileLogcat-recipes.sln --packages /tmp/codeql-scratch-ede23a8d644e0155/dbs/csharp/working/packages /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal /p:TargetFrameworkRootPath=/tmp/codeql-scratch-ede23a8d644e0155/dbs/csharp/working/emptyFakeDotnetRoot /p:NetCoreTargetingPackRoot=/tmp/codeql-scratch-ede23a8d644e0155/dbs/csharp/working/emptyFakeDotnetRoot /p:AllowMissingPrunePackageData=true (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants