/****************************************************************************** * The MIT License (MIT) * * Copyright (c) 2014 Crytek * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. ******************************************************************************/ using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Threading; using System.Text; using System.Windows.Forms; using renderdocui.Code; using renderdoc; namespace renderdocui.Windows.Dialogs { // this window lists the remote hosts the user has configured, and queries for any open connections // on any of them that indicate an application with renderdoc hooks that's running. public partial class RemoteHostSelect : Form { MainWindow m_Main; Core m_Core; class RemoteConnect { public RemoteConnect(string h, UInt32 i) { host = h; ident = i; } public string host; public UInt32 ident; }; public RemoteHostSelect(Core core, MainWindow main) { InitializeComponent(); Icon = global::renderdocui.Properties.Resources.icon; m_Core = core; m_Main = main; hosts.BeginInit(); // localhost should always be available if (!m_Core.Config.RecentHosts.Contains("localhost")) m_Core.Config.RecentHosts.Add("localhost"); foreach (var h in m_Core.Config.RecentHosts) { AddHost(h); } hosts.EndInit(); } // we kick off a thread per host to query for any open connections. The // interfaces on the C++ side should be thread-safe to cope with this private void AddHost(String host) { TreelistView.Node node = new TreelistView.Node(new object[] { host, "" }); node.Italic = true; node.Image = global::renderdocui.Properties.Resources.hourglass; hosts.Nodes.Add(node); refresh.Enabled = false; Thread th = Helpers.NewThread(new ParameterizedThreadStart(LookupHostConnections)); th.Start(node); } private class AvailableRemote { public AvailableRemote(string t, string a, string b) { Target = t; API = a; Busy = b; } public string Target, API, Busy; } private static int lookupsInProgress = 0; private static Mutex lookupMutex = new Mutex(); // this function looks up the remote connections and for each one open // queries it for the API, target (usually executable name) and if any user is already connected private static void LookupHostConnections(object o) { { lookupMutex.WaitOne(); lookupsInProgress++; lookupMutex.ReleaseMutex(); } TreelistView.Node node = o as TreelistView.Node; Control p = node.OwnerView; while (p.Parent != null) p = p.Parent; RemoteHostSelect rhs = p as RemoteHostSelect; string hostname = node["Hostname"] as string; var idents = StaticExports.EnumerateRemoteConnections(hostname); var remotes = new Dictionary(); string username = System.Security.Principal.WindowsIdentity.GetCurrent().Name; foreach (var i in idents) { if (i != 0) { try { var conn = StaticExports.CreateRemoteAccessConnection(hostname, i, username, false); var data = new AvailableRemote(conn.Target, conn.API, conn.BusyClient); conn.Shutdown(); remotes.Add(i, data); } catch (ApplicationException) { } } } if (node.OwnerView.Visible) { node.OwnerView.BeginInvoke((MethodInvoker)delegate { node.OwnerView.BeginUpdate(); node.Italic = false; node.Image = null; foreach (var kv in remotes) { node.Nodes.Add(new TreelistView.Node(new object[] { kv.Value.Target, kv.Value.API, kv.Value.Busy })).Tag = new RemoteConnect(hostname, kv.Key); node.Bold = true; } node.OwnerView.EndUpdate(); }); } { lookupMutex.WaitOne(); lookupsInProgress--; lookupMutex.ReleaseMutex(); } if(!rhs.IsDisposed && rhs.Visible) rhs.BeginInvoke((MethodInvoker)delegate { rhs.LookupComplete(); }); } // don't allow the user to refresh until all pending connections have been checked // (to stop flooding) private void LookupComplete() { if (lookupsInProgress == 0) { refresh.Enabled = true; } } private void hosts_AfterSelect(object sender, TreeViewEventArgs e) { if (hosts.SelectedNode != null && hosts.SelectedNode.Tag != null) { connect.Enabled = true; } else { connect.Enabled = false; } } private void ConnectToHost(TreelistView.Node node) { if (node != null && node.Tag != null) { var connect = node.Tag as RemoteConnect; var live = new LiveCapture(m_Core, connect.host, connect.ident, m_Main); m_Main.ShowLiveCapture(live); Close(); } } private void AddNewHost() { if (hostname.Text.Trim() != "" && !m_Core.Config.RecentHosts.Contains(hostname.Text.ToLower())) { m_Core.Config.RecentHosts.Add(hostname.Text); m_Core.Config.Serialize(Core.ConfigFilename); hosts.BeginUpdate(); AddHost(hostname.Text); hosts.EndUpdate(); } hostname.Text = ""; } private void connect_Click(object sender, EventArgs e) { ConnectToHost(hosts.SelectedNode); } private void hosts_NodeDoubleClicked(TreelistView.Node node) { ConnectToHost(node); } private void addhost_Click(object sender, EventArgs e) { AddNewHost(); } private void hostname_KeyPress(object sender, KeyPressEventArgs e) { if (e.KeyChar == '\n' || e.KeyChar == '\r') AddNewHost(); } private void refresh_Click(object sender, EventArgs e) { if (lookupsInProgress > 0) return; refresh.Enabled = false; hosts.BeginUpdate(); foreach (TreelistView.Node n in hosts.Nodes) { n.Nodes.Clear(); n.Italic = true; n.Image = global::renderdocui.Properties.Resources.hourglass; n.Bold = false; Thread th = Helpers.NewThread(new ParameterizedThreadStart(LookupHostConnections)); th.Start(n); } hosts.EndUpdate(); } private void hosts_KeyDown(object sender, KeyEventArgs e) { if (e.KeyData == Keys.Return || e.KeyData == Keys.Enter) { if (connect.Enabled) ConnectToHost(hosts.SelectedNode); } if (e.KeyData == Keys.Delete && hosts.SelectedNode != null && hosts.SelectedNode.Parent == null) { string hostname = hosts.SelectedNode["Hostname"] as string; if(hostname == "localhost") return; DialogResult res = MessageBox.Show(String.Format("Are you sure you wish to delete {0}?", hostname), "Deleting host", MessageBoxButtons.YesNoCancel); if (res == DialogResult.Cancel || res == DialogResult.No) return; if (res == DialogResult.Yes) { m_Core.Config.RecentHosts.Remove(hosts.SelectedNode["Hostname"] as String); m_Core.Config.Serialize(Core.ConfigFilename); hosts.BeginUpdate(); hosts.Nodes.Remove(hosts.SelectedNode); hosts.EndUpdate(); } } } } }