From cc0ad9baa8f45de0d812b0cad86083fb3b24ca3f Mon Sep 17 00:00:00 2001 From: Tomas Date: Fri, 25 Sep 2026 15:00:35 +0300 Subject: [PATCH 1/3] implement OpenTerminal on linux editor --- .../Editor/AndroidLogcatUtilities.cs | 63 ++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/com.unity.mobile.android-logcat/Editor/AndroidLogcatUtilities.cs b/com.unity.mobile.android-logcat/Editor/AndroidLogcatUtilities.cs index 60cb6cbc..b164b151 100644 --- a/com.unity.mobile.android-logcat/Editor/AndroidLogcatUtilities.cs +++ b/com.unity.mobile.android-logcat/Editor/AndroidLogcatUtilities.cs @@ -317,7 +317,7 @@ public static void OpenTerminal(string workingDirectory) switch (Application.platform) { case RuntimePlatform.WindowsEditor: - System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo("cmd.exe") { WorkingDirectory = workingDirectory }); + System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo("cmd.exe") { WorkingDirectory = workingDirectory, UseShellExecute = true }); break; case RuntimePlatform.OSXEditor: var pathsToCheck = new[] @@ -335,11 +335,72 @@ public static void OpenTerminal(string workingDirectory) } throw new Exception(string.Format("Failed to launch Terminal app, tried following paths:\n{0}", string.Join("\n", pathsToCheck))); + case RuntimePlatform.LinuxEditor: + OpenLinuxTerminal(workingDirectory); + break; default: throw new Exception("Don't know how to open terminal on " + Application.platform.ToString()); } } + private static void OpenLinuxTerminal(string workingDirectory) + { + // Terminal executable and the arguments used to set its working directory. + // Terminals with no arguments inherit the working directory from ProcessStartInfo. + var terminals = new List>(); + + var userTerminal = Environment.GetEnvironmentVariable("TERMINAL"); + if (!string.IsNullOrEmpty(userTerminal)) + terminals.Add(new KeyValuePair(userTerminal, string.Empty)); + + terminals.Add(new KeyValuePair("x-terminal-emulator", string.Empty)); + terminals.Add(new KeyValuePair("gnome-terminal", $"--working-directory=\"{workingDirectory}\"")); + terminals.Add(new KeyValuePair("konsole", $"--workdir \"{workingDirectory}\"")); + terminals.Add(new KeyValuePair("xfce4-terminal", $"--working-directory=\"{workingDirectory}\"")); + terminals.Add(new KeyValuePair("mate-terminal", $"--working-directory=\"{workingDirectory}\"")); + terminals.Add(new KeyValuePair("tilix", $"--working-directory=\"{workingDirectory}\"")); + terminals.Add(new KeyValuePair("alacritty", $"--working-directory \"{workingDirectory}\"")); + terminals.Add(new KeyValuePair("kitty", $"--directory \"{workingDirectory}\"")); + terminals.Add(new KeyValuePair("xterm", string.Empty)); + + foreach (var terminal in terminals) + { + var path = FindExecutableInPath(terminal.Key); + if (path == null) + continue; + + System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(path, terminal.Value) + { + WorkingDirectory = workingDirectory, + UseShellExecute = false + }); + return; + } + + throw new Exception(string.Format("Failed to launch terminal, tried following terminals:\n{0}", string.Join("\n", terminals.Select(t => t.Key)))); + } + + private static string FindExecutableInPath(string executable) + { + if (Path.IsPathRooted(executable)) + return File.Exists(executable) ? executable : null; + + var paths = Environment.GetEnvironmentVariable("PATH"); + if (string.IsNullOrEmpty(paths)) + return null; + + foreach (var dir in paths.Split(Path.PathSeparator)) + { + if (string.IsNullOrEmpty(dir)) + continue; + var fullPath = Path.Combine(dir, executable); + if (File.Exists(fullPath)) + return fullPath; + } + + return null; + } + public static Version ParseVersionLegacy(string versionString) { int major = 0; From c7443a9da4c31569b37c5c611c0f5ebfed2dbbed Mon Sep 17 00:00:00 2001 From: Tomas Date: Fri, 25 Sep 2026 15:00:41 +0300 Subject: [PATCH 2/3] CHANGELOG --- com.unity.mobile.android-logcat/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/com.unity.mobile.android-logcat/CHANGELOG.md b/com.unity.mobile.android-logcat/CHANGELOG.md index cfe34db8..2a2d481a 100644 --- a/com.unity.mobile.android-logcat/CHANGELOG.md +++ b/com.unity.mobile.android-logcat/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changes & Improvements: - Unity 6.0 or later is required. + - Tools->Open Terminal is now supported on Linux Editor. + - Fixed Tools->Open Terminal not opening a window on Windows Editor when running on CoreCLR. ## [1.4.7] - 2025-12-12 ### Fixes & Improvements From 7b60fdd1c07be32144fd43cedb3036216abc0720 Mon Sep 17 00:00:00 2001 From: Tomas Date: Fri, 25 Sep 2026 17:02:47 +0300 Subject: [PATCH 3/3] Address review: pass Linux terminal arguments via ArgumentList - Split $TERMINAL into executable and arguments without invoking a shell, so values like 'wezterm start' work. - Pass the working directory as separate arguments via ProcessStartInfo.ArgumentList instead of embedding it in a quoted Arguments string, so paths containing quotes are preserved. Co-Authored-By: Claude Opus 5.5 --- .../Editor/AndroidLogcatUtilities.cs | 109 ++++++++++++++---- 1 file changed, 88 insertions(+), 21 deletions(-) diff --git a/com.unity.mobile.android-logcat/Editor/AndroidLogcatUtilities.cs b/com.unity.mobile.android-logcat/Editor/AndroidLogcatUtilities.cs index b164b151..07c8a349 100644 --- a/com.unity.mobile.android-logcat/Editor/AndroidLogcatUtilities.cs +++ b/com.unity.mobile.android-logcat/Editor/AndroidLogcatUtilities.cs @@ -345,39 +345,106 @@ public static void OpenTerminal(string workingDirectory) private static void OpenLinuxTerminal(string workingDirectory) { - // Terminal executable and the arguments used to set its working directory. - // Terminals with no arguments inherit the working directory from ProcessStartInfo. - var terminals = new List>(); - - var userTerminal = Environment.GetEnvironmentVariable("TERMINAL"); - if (!string.IsNullOrEmpty(userTerminal)) - terminals.Add(new KeyValuePair(userTerminal, string.Empty)); - - terminals.Add(new KeyValuePair("x-terminal-emulator", string.Empty)); - terminals.Add(new KeyValuePair("gnome-terminal", $"--working-directory=\"{workingDirectory}\"")); - terminals.Add(new KeyValuePair("konsole", $"--workdir \"{workingDirectory}\"")); - terminals.Add(new KeyValuePair("xfce4-terminal", $"--working-directory=\"{workingDirectory}\"")); - terminals.Add(new KeyValuePair("mate-terminal", $"--working-directory=\"{workingDirectory}\"")); - terminals.Add(new KeyValuePair("tilix", $"--working-directory=\"{workingDirectory}\"")); - terminals.Add(new KeyValuePair("alacritty", $"--working-directory \"{workingDirectory}\"")); - terminals.Add(new KeyValuePair("kitty", $"--directory \"{workingDirectory}\"")); - terminals.Add(new KeyValuePair("xterm", string.Empty)); + // Terminal command lines, including the arguments used to set the working directory where supported. + // Terminals without such arguments inherit the working directory from ProcessStartInfo. + // Arguments are passed via ArgumentList, so paths containing spaces or quotes are preserved. + var terminals = new List(); + + var userTerminal = SplitCommandLine(Environment.GetEnvironmentVariable("TERMINAL")); + if (userTerminal.Length > 0) + terminals.Add(userTerminal); + + terminals.Add(new[] { "x-terminal-emulator" }); + terminals.Add(new[] { "gnome-terminal", "--working-directory=" + workingDirectory }); + terminals.Add(new[] { "konsole", "--workdir", workingDirectory }); + terminals.Add(new[] { "xfce4-terminal", "--working-directory=" + workingDirectory }); + terminals.Add(new[] { "mate-terminal", "--working-directory=" + workingDirectory }); + terminals.Add(new[] { "tilix", "--working-directory=" + workingDirectory }); + terminals.Add(new[] { "alacritty", "--working-directory", workingDirectory }); + terminals.Add(new[] { "kitty", "--directory", workingDirectory }); + terminals.Add(new[] { "xterm" }); foreach (var terminal in terminals) { - var path = FindExecutableInPath(terminal.Key); + var path = FindExecutableInPath(terminal[0]); if (path == null) continue; - System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(path, terminal.Value) + var startInfo = new System.Diagnostics.ProcessStartInfo(path) { WorkingDirectory = workingDirectory, UseShellExecute = false - }); + }; + foreach (var arg in terminal.Skip(1)) + startInfo.ArgumentList.Add(arg); + + System.Diagnostics.Process.Start(startInfo); return; } - throw new Exception(string.Format("Failed to launch terminal, tried following terminals:\n{0}", string.Join("\n", terminals.Select(t => t.Key)))); + throw new Exception(string.Format("Failed to launch terminal, tried following terminals:\n{0}", string.Join("\n", terminals.Select(t => t[0])))); + } + + /// + /// Splits a command line like 'wezterm start' or '"/opt/my term/bin/term" -e' into executable and arguments, without invoking a shell. + /// Supports single quotes, double quotes and backslash escapes. + /// + internal static string[] SplitCommandLine(string commandLine) + { + var result = new List(); + if (string.IsNullOrEmpty(commandLine)) + return result.ToArray(); + + var current = new System.Text.StringBuilder(); + var hasToken = false; + char quote = '\0'; + for (int i = 0; i < commandLine.Length; i++) + { + var c = commandLine[i]; + if (quote == '\'') + { + if (c == '\'') + quote = '\0'; + else + current.Append(c); + } + else if (c == '\\' && i + 1 < commandLine.Length) + { + current.Append(commandLine[++i]); + hasToken = true; + } + else if (quote == '"') + { + if (c == '"') + quote = '\0'; + else + current.Append(c); + } + else if (c == '\'' || c == '"') + { + quote = c; + hasToken = true; + } + else if (char.IsWhiteSpace(c)) + { + if (hasToken) + { + result.Add(current.ToString()); + current.Clear(); + hasToken = false; + } + } + else + { + current.Append(c); + hasToken = true; + } + } + + if (hasToken) + result.Add(current.ToString()); + + return result.ToArray(); } private static string FindExecutableInPath(string executable)