diff --git a/renderdoc/os/os_specific.h b/renderdoc/os/os_specific.h index 3a70be85e..1f74e9a81 100644 --- a/renderdoc/os/os_specific.h +++ b/renderdoc/os/os_specific.h @@ -82,7 +82,13 @@ void ApplyEnvironmentModification(); void StartGlobalHook(const char *pathmatch, const char *logfile, const CaptureOptions *opts); uint32_t InjectIntoProcess(uint32_t pid, EnvironmentModification *env, const char *logfile, const CaptureOptions *opts, bool waitForExit); -uint32_t LaunchProcess(const char *app, const char *workingDir, const char *cmdLine); +struct ProcessResult +{ + string strStdout, strStderror; + int retCode; +}; +uint32_t LaunchProcess(const char *app, const char *workingDir, const char *cmdLine, + ProcessResult *result = NULL); uint32_t LaunchAndInjectIntoProcess(const char *app, const char *workingDir, const char *cmdLine, EnvironmentModification *env, const char *logfile, const CaptureOptions *opts, bool waitForExit); diff --git a/renderdoc/os/posix/posix_process.cpp b/renderdoc/os/posix/posix_process.cpp index 3931c826b..b8b4ef5b4 100644 --- a/renderdoc/os/posix/posix_process.cpp +++ b/renderdoc/os/posix/posix_process.cpp @@ -348,7 +348,8 @@ uint32_t Process::InjectIntoProcess(uint32_t pid, EnvironmentModification *env, return 0; } -uint32_t Process::LaunchProcess(const char *app, const char *workingDir, const char *cmdLine) +uint32_t Process::LaunchProcess(const char *app, const char *workingDir, const char *cmdLine, + ProcessResult *result) { if(app == NULL || app[0] == 0) { diff --git a/renderdoc/os/win32/win32_process.cpp b/renderdoc/os/win32/win32_process.cpp index 8e4bd3e89..17f5fbe14 100644 --- a/renderdoc/os/win32/win32_process.cpp +++ b/renderdoc/os/win32/win32_process.cpp @@ -371,7 +371,8 @@ void InjectFunctionCall(HANDLE hProcess, uintptr_t renderdoc_remote, const char VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE); } -static PROCESS_INFORMATION RunProcess(const char *app, const char *workingDir, const char *cmdLine) +static PROCESS_INFORMATION RunProcess(const char *app, const char *workingDir, const char *cmdLine, + HANDLE *phChildStdOutput_Rd, HANDLE *phChildStdError_Rd) { PROCESS_INFORMATION pi; STARTUPINFO si; @@ -422,12 +423,44 @@ static PROCESS_INFORMATION RunProcess(const char *app, const char *workingDir, c wcscat_s(paramsAlloc, len, wcmd.c_str()); } + HANDLE hChildStdOutput_Wr = 0, hChildStdError_Wr = 0; + if(phChildStdOutput_Rd) + { + RDCASSERT(phChildStdError_Rd); + + SECURITY_ATTRIBUTES sa; + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.bInheritHandle = TRUE; + sa.lpSecurityDescriptor = NULL; + + if(!CreatePipe(phChildStdOutput_Rd, &hChildStdOutput_Wr, &sa, 0)) + RDCERR("Could not create pipe to read stdout"); + if(!SetHandleInformation(*phChildStdOutput_Rd, HANDLE_FLAG_INHERIT, 0)) + RDCERR("Could not set pipe handle information"); + + if(!CreatePipe(phChildStdError_Rd, &hChildStdError_Wr, &sa, 0)) + RDCERR("Could not create pipe to read stdout"); + if(!SetHandleInformation(*phChildStdError_Rd, HANDLE_FLAG_INHERIT, 0)) + RDCERR("Could not set pipe handle information"); + + si.dwFlags |= STARTF_USESHOWWINDOW // Hide the command prompt window from showing. + | STARTF_USESTDHANDLES; + si.hStdOutput = hChildStdOutput_Wr; + } + RDCLOG("Running process %s", app); - BOOL retValue = CreateProcessW(NULL, paramsAlloc, &pSec, &tSec, false, + BOOL retValue = CreateProcessW(NULL, paramsAlloc, &pSec, &tSec, + true, // Need to inherit handles for ReadFile to read stdout CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT, NULL, workdir.c_str(), &si, &pi); + if(phChildStdOutput_Rd) + { + CloseHandle(hChildStdOutput_Wr); + CloseHandle(hChildStdError_Wr); + } + SAFE_DELETE_ARRAY(paramsAlloc); if(!retValue) @@ -711,9 +744,13 @@ uint32_t Process::InjectIntoProcess(uint32_t pid, EnvironmentModification *env, return controlident; } -uint32_t Process::LaunchProcess(const char *app, const char *workingDir, const char *cmdLine) +uint32_t Process::LaunchProcess(const char *app, const char *workingDir, const char *cmdLine, + ProcessResult *result) { - PROCESS_INFORMATION pi = RunProcess(app, workingDir, cmdLine); + HANDLE hChildStdOutput_Rd, hChildStdError_Rd; + + PROCESS_INFORMATION pi = RunProcess(app, workingDir, cmdLine, result ? &hChildStdOutput_Rd : NULL, + result ? &hChildStdError_Rd : NULL); if(pi.dwProcessId == 0) { @@ -724,6 +761,35 @@ uint32_t Process::LaunchProcess(const char *app, const char *workingDir, const c RDCLOG("Launched process '%s' with '%s'", app, cmdLine); ResumeThread(pi.hThread); + + if(result) + { + result->strStdout = ""; + result->strStderror = ""; + for(;;) + { + char chBuf[1000]; + DWORD dwOutputRead, dwErrorRead; + + BOOL success = ReadFile(hChildStdOutput_Rd, chBuf, sizeof(chBuf), &dwOutputRead, NULL); + string s(chBuf, dwOutputRead); + result->strStdout += s; + + success = ReadFile(hChildStdError_Rd, chBuf, sizeof(chBuf), &dwErrorRead, NULL); + s = string(chBuf, dwErrorRead); + result->strStderror += s; + + if(!success && !dwOutputRead && !dwErrorRead) + break; + } + + CloseHandle(hChildStdOutput_Rd); + CloseHandle(hChildStdError_Rd); + + WaitForSingleObject(pi.hProcess, INFINITE); + GetExitCodeProcess(pi.hProcess, (LPDWORD)&result->retCode); + } + CloseHandle(pi.hThread); CloseHandle(pi.hProcess); @@ -745,7 +811,7 @@ uint32_t Process::LaunchAndInjectIntoProcess(const char *app, const char *workin return 0; } - PROCESS_INFORMATION pi = RunProcess(app, workingDir, cmdLine); + PROCESS_INFORMATION pi = RunProcess(app, workingDir, cmdLine, NULL, NULL); if(pi.dwProcessId == 0) return 0; diff --git a/renderdoc/replay/entry_points.cpp b/renderdoc/replay/entry_points.cpp index 92d1cea8a..d42ac708a 100644 --- a/renderdoc/replay/entry_points.cpp +++ b/renderdoc/replay/entry_points.cpp @@ -23,6 +23,7 @@ * THE SOFTWARE. ******************************************************************************/ +#include #include "api/replay/renderdoc_replay.h" #include "common/common.h" #include "core/core.h" @@ -31,6 +32,7 @@ #include "maths/formatpacking.h" #include "replay/replay_renderer.h" #include "serialise/serialiser.h" +#include "serialise/string_utils.h" // these entry points are for the replay/analysis side - not for the application. @@ -590,4 +592,52 @@ extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_EndSelfHostCapture(const ch return; rdoc->EndFrameCapture(NULL, NULL); -} \ No newline at end of file +} + +string adbExecCommand(const string &args) +{ + string adbExePath = RenderDoc::Inst().GetConfigSetting("adbExePath"); + Process::ProcessResult result; + Process::LaunchProcess(adbExePath.c_str(), "", args.c_str(), &result); + RDCLOG("COMMAND: adb %s", args.c_str()); + if(result.strStdout.length()) + // This could be an error (i.e. no package), or just regular output from adb devices. + RDCLOG("STDOUT:\n%s", result.strStdout.c_str()); + return result.strStdout; +} +void adbForwardPorts() +{ + adbExecCommand(StringFormat::Fmt("forward tcp:%i tcp:%i", + RenderDoc_RemoteServerPort + RenderDoc_AndroidPortOffset, + RenderDoc_RemoteServerPort)); + adbExecCommand(StringFormat::Fmt("forward tcp:%i tcp:%i", + RenderDoc_FirstTargetControlPort + RenderDoc_AndroidPortOffset, + RenderDoc_FirstTargetControlPort)); +} + +extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_EnumerateAndroidDevices(rdctype::str *deviceList) +{ + string adbStdout = adbExecCommand("devices"); + + using namespace std; + istringstream stdoutStream(adbStdout); + string ret; + string line; + while(getline(stdoutStream, line)) + { + vector tokens; + split(line, tokens, '\t'); + if(tokens.size() == 2 && trim(tokens[1]) == "device") + { + if(ret.length()) + ret += ","; + ret += tokens[0]; + } + } + + if(ret.size()) + adbForwardPorts(); // Forward the ports so we can see if a remoteserver/captured app is + // already running. + + *deviceList = ret; +} diff --git a/renderdoc/serialise/string_utils.cpp b/renderdoc/serialise/string_utils.cpp index d771f4fe3..4afc17e66 100644 --- a/renderdoc/serialise/string_utils.cpp +++ b/renderdoc/serialise/string_utils.cpp @@ -77,8 +77,9 @@ wstring strupper(const wstring &str) std::string trim(const std::string &str) { - size_t start = str.find_first_not_of("\t \n"); - size_t end = str.find_last_not_of("\t \n"); + const char *whitespace = "\t \n\r"; + size_t start = str.find_first_not_of(whitespace); + size_t end = str.find_last_not_of(whitespace); // no non-whitespace characters, return the empty string if(start == std::string::npos) diff --git a/renderdocui/Code/PersistantConfig.cs b/renderdocui/Code/PersistantConfig.cs index 21cd8b3b2..ef46dc5dc 100644 --- a/renderdocui/Code/PersistantConfig.cs +++ b/renderdocui/Code/PersistantConfig.cs @@ -175,6 +175,7 @@ namespace renderdocui.Code public string LastCaptureExe = ""; public List RecentCaptureSettings = new List(); public int CallstackLevelSkip = 0; + public string AdbExecutablePath = ""; // for historical reasons, this was named CaptureSavePath [XmlElement("CaptureSavePath")] @@ -401,5 +402,30 @@ namespace renderdocui.Code return c; } + + public void AddAndroidHosts() + { + for (int i = RemoteHosts.Count - 1; i >= 0; i--) + { + if (RemoteHosts[i].Hostname.StartsWith("adb:")) + RemoteHosts.RemoveAt(i); + } + + string adbExePath = File.Exists(AdbExecutablePath) ? AdbExecutablePath : ""; + + // Set the config setting as it will be reused when we start the remoteserver etc. + StaticExports.SetConfigSetting("adbExePath", adbExePath); + + if (adbExePath.Length == 0) + return;// adb path must be non-empty in the Options dialog. + + string[] androidHosts = StaticExports.EnumerateAndroidDevices(); + foreach(string hostName in androidHosts) + { + RemoteHost host = new RemoteHost(); + host.Hostname = "adb:" + hostName; + RemoteHosts.Add(host); + } + } } } diff --git a/renderdocui/Interop/StaticExports.cs b/renderdocui/Interop/StaticExports.cs index 2b02adeb2..8cad3400c 100644 --- a/renderdocui/Interop/StaticExports.cs +++ b/renderdocui/Interop/StaticExports.cs @@ -110,6 +110,9 @@ namespace renderdoc [DllImport("renderdoc.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] private static extern void RENDERDOC_FreeEnvironmentModificationList(IntPtr mem); + [DllImport("renderdoc.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] + private static extern ReplaySupport RENDERDOC_EnumerateAndroidDevices(IntPtr driverName); + [DllImport("renderdoc.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] private static extern void RENDERDOC_StartSelfHostCapture(IntPtr dllname); @@ -394,5 +397,17 @@ namespace renderdoc return ret; } + + public static string[] EnumerateAndroidDevices() + { + IntPtr driverListInt = CustomMarshal.Alloc(typeof(templated_array)); + + RENDERDOC_EnumerateAndroidDevices(driverListInt); + + string driverList = CustomMarshal.TemplatedArrayToString(driverListInt, true); + CustomMarshal.Free(driverListInt); + + return driverList.Split(new char[]{','}, StringSplitOptions.RemoveEmptyEntries); + } } } diff --git a/renderdocui/Windows/Dialogs/RemoteManager.cs b/renderdocui/Windows/Dialogs/RemoteManager.cs index cf4dd9554..a203aa567 100644 --- a/renderdocui/Windows/Dialogs/RemoteManager.cs +++ b/renderdocui/Windows/Dialogs/RemoteManager.cs @@ -120,6 +120,7 @@ namespace renderdocui.Windows.Dialogs { hosts.BeginInit(); + m_Core.Config.AddAndroidHosts(); foreach (var h in m_Core.Config.RemoteHosts) AddHost(h); diff --git a/renderdocui/Windows/Dialogs/SettingsDialog.Designer.cs b/renderdocui/Windows/Dialogs/SettingsDialog.Designer.cs index 933a579cd..24e858408 100644 --- a/renderdocui/Windows/Dialogs/SettingsDialog.Designer.cs +++ b/renderdocui/Windows/Dialogs/SettingsDialog.Designer.cs @@ -56,6 +56,9 @@ System.Windows.Forms.Label label9; System.Windows.Forms.Label label16; System.Windows.Forms.Label label17; + System.Windows.Forms.GroupBox groupBox7; + System.Windows.Forms.TableLayoutPanel tableLayoutPanel8; + System.Windows.Forms.Label label36; TreelistView.TreeListColumn treeListColumn3 = ((TreelistView.TreeListColumn)(new TreelistView.TreeListColumn("Section", "Section"))); this.settingsTabs = new renderdocui.Controls.TablessControl(); this.generalTab = new System.Windows.Forms.TabPage(); @@ -100,6 +103,9 @@ this.EventBrowser_HideEmpty = new System.Windows.Forms.CheckBox(); this.EventBrowser_ApplyColours = new System.Windows.Forms.CheckBox(); this.EventBrowser_ColourEventRow = new System.Windows.Forms.CheckBox(); + this.androidTab = new System.Windows.Forms.TabPage(); + this.browseAdbPath = new System.Windows.Forms.Button(); + this.adbPath = new System.Windows.Forms.TextBox(); this.pagesTree = new TreelistView.TreeListView(); this.ok = new System.Windows.Forms.Button(); this.toolTip = new System.Windows.Forms.ToolTip(this.components); @@ -131,6 +137,9 @@ label9 = new System.Windows.Forms.Label(); label16 = new System.Windows.Forms.Label(); label17 = new System.Windows.Forms.Label(); + groupBox7 = new System.Windows.Forms.GroupBox(); + tableLayoutPanel8 = new System.Windows.Forms.TableLayoutPanel(); + label36 = new System.Windows.Forms.Label(); tableLayoutPanel1.SuspendLayout(); this.settingsTabs.SuspendLayout(); this.generalTab.SuspendLayout(); @@ -182,6 +191,7 @@ this.settingsTabs.Controls.Add(this.texViewTab); this.settingsTabs.Controls.Add(this.shadViewTab); this.settingsTabs.Controls.Add(this.eventTab); + this.settingsTabs.Controls.Add(this.androidTab); this.settingsTabs.Dock = System.Windows.Forms.DockStyle.Fill; this.settingsTabs.Location = new System.Drawing.Point(164, 3); this.settingsTabs.Multiline = true; @@ -1157,6 +1167,93 @@ this.EventBrowser_ColourEventRow.UseVisualStyleBackColor = true; this.EventBrowser_ColourEventRow.CheckedChanged += new System.EventHandler(this.EventBrowser_ColourEventRow_CheckedChanged); // + // androidTab + // + this.androidTab.Controls.Add(groupBox7); + this.androidTab.Location = new System.Drawing.Point(25, 4); + this.androidTab.Name = "tabPage1"; + this.androidTab.Padding = new System.Windows.Forms.Padding(3); + this.androidTab.Size = new System.Drawing.Size(465, 503); + this.androidTab.TabIndex = 5; + this.androidTab.Text = "Android"; + this.androidTab.UseVisualStyleBackColor = true; + // + // groupBox7 + // + groupBox7.Controls.Add(tableLayoutPanel8); + groupBox7.Dock = System.Windows.Forms.DockStyle.Fill; + groupBox7.Location = new System.Drawing.Point(3, 3); + groupBox7.Margin = new System.Windows.Forms.Padding(4); + groupBox7.Name = "groupBox7"; + groupBox7.Padding = new System.Windows.Forms.Padding(4); + groupBox7.Size = new System.Drawing.Size(459, 497); + groupBox7.TabIndex = 1; + groupBox7.TabStop = false; + groupBox7.Text = "Android"; + // + // tableLayoutPanel8 + // + tableLayoutPanel8.ColumnCount = 2; + tableLayoutPanel8.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + tableLayoutPanel8.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + tableLayoutPanel8.Controls.Add(this.browseAdbPath, 1, 1); + tableLayoutPanel8.Controls.Add(label36, 0, 0); + tableLayoutPanel8.Controls.Add(this.adbPath, 0, 1); + tableLayoutPanel8.Dock = System.Windows.Forms.DockStyle.Fill; + tableLayoutPanel8.Location = new System.Drawing.Point(4, 19); + tableLayoutPanel8.Margin = new System.Windows.Forms.Padding(4); + tableLayoutPanel8.Name = "tableLayoutPanel8"; + tableLayoutPanel8.RowCount = 3; + tableLayoutPanel8.RowStyles.Add(new System.Windows.Forms.RowStyle()); + tableLayoutPanel8.RowStyles.Add(new System.Windows.Forms.RowStyle()); + tableLayoutPanel8.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + tableLayoutPanel8.Size = new System.Drawing.Size(451, 474); + tableLayoutPanel8.TabIndex = 0; + // + // browseAdbPath + // + this.browseAdbPath.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.browseAdbPath.Location = new System.Drawing.Point(327, 29); + this.browseAdbPath.Margin = new System.Windows.Forms.Padding(4); + this.browseAdbPath.Name = "browseAdbPath"; + this.browseAdbPath.Size = new System.Drawing.Size(120, 28); + this.browseAdbPath.TabIndex = 7; + this.browseAdbPath.Text = "Browse"; + this.toolTip.SetToolTip(this.browseAdbPath, "Changes the directory where capture files are saved after being created, until sa" + + "ved manually or deleted.\r\n\r\nDefaults to %TEMP%."); + this.browseAdbPath.UseVisualStyleBackColor = true; + this.browseAdbPath.Click += new System.EventHandler(this.browseAdbPath_Click); + // + // label36 + // + label36.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + label36.AutoSize = true; + label36.Location = new System.Drawing.Point(4, 0); + label36.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + label36.MinimumSize = new System.Drawing.Size(0, 25); + label36.Name = "label36"; + label36.Size = new System.Drawing.Size(315, 25); + label36.TabIndex = 14; + label36.Text = "Android ADB executable path"; + label36.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + this.toolTip.SetToolTip(label36, "Changes the directory where capture files are saved after being created, until sa" + + "ved manually or deleted.\r\n\r\nDefaults to %TEMP%."); + // + // adbPath + // + this.adbPath.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.adbPath.Location = new System.Drawing.Point(4, 29); + this.adbPath.Margin = new System.Windows.Forms.Padding(4); + this.adbPath.Name = "adbPath"; + this.adbPath.Size = new System.Drawing.Size(315, 22); + this.adbPath.TabIndex = 24; + this.toolTip.SetToolTip(this.adbPath, "Changes the directory where capture files are saved after being created, until sa" + + "ved manually or deleted.\r\n\r\nDefaults to %TEMP%."); + this.adbPath.TextChanged += new System.EventHandler(this.adbPath_TextChanged); + // // pagesTree // treeListColumn3.AutoSize = true; @@ -1287,5 +1384,8 @@ private System.Windows.Forms.Label label23; private System.Windows.Forms.Label label22; private System.Windows.Forms.Label label24; + private System.Windows.Forms.TabPage androidTab; + private System.Windows.Forms.Button browseAdbPath; + private System.Windows.Forms.TextBox adbPath; } } \ No newline at end of file diff --git a/renderdocui/Windows/Dialogs/SettingsDialog.cs b/renderdocui/Windows/Dialogs/SettingsDialog.cs index 494d7b8b1..5b5c8fafb 100644 --- a/renderdocui/Windows/Dialogs/SettingsDialog.cs +++ b/renderdocui/Windows/Dialogs/SettingsDialog.cs @@ -64,6 +64,7 @@ namespace renderdocui.Windows.Dialogs externalDisassemblerEnabledCheckbox.Checked = m_Core.Config.ExternalDisassemblerEnabled; externalDisassemblerArgs.Text = m_Core.Config.GetDefaultExternalDisassembler().args; externalDisassemblePath.Text = m_Core.Config.GetDefaultExternalDisassembler().executable; + adbPath.Text = m_Core.Config.AdbExecutablePath; TextureViewer_ResetRange.Checked = m_Core.Config.TextureViewer_ResetRange; TextureViewer_PerTexSettings.Checked = m_Core.Config.TextureViewer_PerTexSettings; @@ -385,5 +386,33 @@ namespace renderdocui.Windows.Dialogs { m_Core.Config.ExternalDisassemblerEnabled = externalDisassemblerEnabledCheckbox.Checked; } + + private void browseAdbPath_Click(object sender, EventArgs e) + { + var res = browseExtDisassembleDialog.ShowDialog(); + + if (res == DialogResult.Yes || res == DialogResult.OK) + { + try + { + adbPath.Text = browseExtDisassembleDialog.FileName; + m_Core.Config.AdbExecutablePath = adbPath.Text; + } + catch (Exception) + { + } + } + } + + private void adbPath_TextChanged(object sender, EventArgs e) + { + try + { + m_Core.Config.AdbExecutablePath = adbPath.Text; + } + catch (Exception) + { + } + } } } diff --git a/renderdocui/Windows/MainWindow.cs b/renderdocui/Windows/MainWindow.cs index 658fec717..5cce9483b 100644 --- a/renderdocui/Windows/MainWindow.cs +++ b/renderdocui/Windows/MainWindow.cs @@ -180,6 +180,7 @@ namespace renderdocui.Windows Thread remoteStatusThread = Helpers.NewThread(new ThreadStart(() => { + m_Core.Config.AddAndroidHosts(); for (int i = 0; i < m_Core.Config.RemoteHosts.Count; i++) m_Core.Config.RemoteHosts[i].CheckStatus(); }));