Skip to content

Commit

Permalink
Fix breakpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
chrishonselaar committed Apr 23, 2013
1 parent 86c73cc commit 692d04e
Show file tree
Hide file tree
Showing 14 changed files with 276 additions and 69 deletions.
19 changes: 19 additions & 0 deletions ProtoPad Client/EditorHelpers.cs
Expand Up @@ -5,6 +5,7 @@
using System.Runtime.Serialization.Json;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Navigation;

namespace ProtoPad_Client
{
Expand Down Expand Up @@ -213,6 +214,24 @@ public static class UtilityMethods
return tcs.Task;
}

public static Task<NavigationEventArgs> GetNavigatedEventAsync(this object eventSource, string eventName)
{
var tcs = new TaskCompletionSource<NavigationEventArgs>();

var type = eventSource.GetType();
var ev = type.GetEvent(eventName);

NavigatedEventHandler handler = null;
handler = delegate(object sender, NavigationEventArgs e)
{
ev.RemoveEventHandler(eventSource, handler);
tcs.SetResult(e);
};

ev.AddEventHandler(eventSource, handler);
return tcs.Task;
}

public static T JsonDecode<T>(string jsonValue)
{
var serializer = new DataContractJsonSerializer(typeof(T));
Expand Down
29 changes: 16 additions & 13 deletions ProtoPad Client/MainWindow.xaml
Expand Up @@ -14,21 +14,24 @@

<DockPanel Grid.Row="0" Margin="0,0,0,5">
<ToolBar DockPanel.Dock="Top" VerticalAlignment="Top" Height="36" HorizontalAlignment="Stretch">

<Button x:Name="SendCodeButton" Content="Send code" Height="31" IsEnabled="False" VerticalAlignment="Top" Width="75" Click="SendCodeButton_Click"/>
<Button x:Name="LoadAssemblyButton" Content="Send assembly" Height="31" VerticalAlignment="Top" IsEnabled="False" Width="100" Click="LoadAssemblyButton_Click"/>
<Button x:Name="ClearSimulatorWindowButton" Content="Clear simulator window" Height="31" VerticalAlignment="Top" Width="134" Click="ClearSimulatorWindowButton_Click"/>
<Button x:Name="ConnectToAppButton" Content="Discover running apps" Height="31" VerticalAlignment="Top" Width="134" Click="ConnectToAppButton_Click"/>
<ComboBox SelectedValuePath="DeviceAddress" DisplayMemberPath="DeviceName" IsEnabled="False" x:Name="DevicesComboBox" VerticalAlignment="Top" Width="260" Height="31" SelectionChanged="DevicesComboBox_SelectionChanged">
</ComboBox>
<Button x:Name="AddManualIPButton" Content="Add IP" Height="31" VerticalAlignment="Bottom" Width="83" Click="AddManualIPButton_Click"/>
<ComboBox x:Name="CodeTypeComboBox" VerticalAlignment="Top" SelectedValue="Statements" Width="100" Height="31" SelectionChanged="CodeTypeComboBox_SelectionChanged" Margin="0">

<Button x:Name="SendCodeButton" Content="Send code" Height="31" IsEnabled="False" VerticalAlignment="Top" Width="75" Click="SendCodeButton_Click" ToolTip="Send code in editor window to connected app (CTRL+Enter)"/>
<Button x:Name="LoadAssemblyButton" Content="Send assembly" Height="31" VerticalAlignment="Top" IsEnabled="False" Width="100" Click="LoadAssemblyButton_Click" ToolTip="Select and send a (compatible) .Net assembly to the running app. You will be able to reference and use that assembly from your code."/>
<Button x:Name="ClearSimulatorWindowButton" Content="Clear app window" Height="31" VerticalAlignment="Top" Width="121" Click="ClearSimulatorWindowButton_Click" ToolTip="Quickly remove all components from your app's main view"/>
<ComboBox x:Name="CodeTypeComboBox" VerticalAlignment="Top" SelectedValue="Statements" Width="100" Height="31" SelectionChanged="CodeTypeComboBox_SelectionChanged" Margin="0" ToolTip="Select what mode to code in: use 'Expression' for quick oneline commands and inspections (no need to use Dump()), use 'Statements' for multi-line code, use 'Program' in order to be able to use functions and classes.">
<sys:String>Expression</sys:String>
<sys:String>Statements</sys:String>
<sys:String>Program</sys:String>
</ComboBox>
<Button x:Name="AboutHelpButton" Content="About/help" Height="31" VerticalAlignment="Top" Width="83" Click="AboutHelpButton_Click"/>
<Button x:Name="ConnectToAppButton" Content="Find servers" Height="31" VerticalAlignment="Top" Width="89" Click="ConnectToAppButton_Click" ToolTip="Autodiscover (through mDNS) any running ProtoPad-enabled apps, and show them in the adjacent dropdown so you can select and connect to one."/>
<ComboBox SelectedValuePath="DeviceAddress" DisplayMemberPath="DeviceName" IsEnabled="False" x:Name="DevicesComboBox" VerticalAlignment="Top" Width="187" Height="31" SelectionChanged="DevicesComboBox_SelectionChanged" Margin="0" ToolTip="Shows the list of auto-discovered ProtoPad-enabled apps on your local machine or network. If no apps are shown, try using the 'Find servers' button or alternatively use 'Add IP' to connect to an app manually.">
</ComboBox>
<Button x:Name="AddManualIPButton" Content="Add IP" Height="31" VerticalAlignment="Bottom" Width="83" Click="AddManualIPButton_Click" ToolTip="If auto-discovery did not locate your app, use this button to add your app's IP address and port manually. You can find this information on the similarly named properties of the ProtoPadServer object that you created within your app."/>
<Button x:Name="ConfigureAndroidEmulatorButton" Content="New Android emu" Height="31" VerticalAlignment="Bottom" Width="109" ToolTip="Find and configure a new Android emulator. If you are using an Android emulator (AVD) with ProtoPad for the first time, it will probably not be set up to share its ports with your local machine. Use this button to automatically locate your running emulator, connect to it through Telnet, and set up port forwarding. All this should be done automatically. Just make sure to have only one emulator running." Click="ConfigureAndroidEmulatorButton_Click"/>

<Button x:Name="AboutHelpButton" Content="About/help" Height="31" VerticalAlignment="Top" Width="83" Click="AboutHelpButton_Click" ToolTip="Instructions and information about the app author and leveraged components."/>


</ToolBar>
<syntaxeditor:SyntaxEditor DockPanel.Dock="Bottom" x:Name="CodeEditor" IsIndicatorMarginVisible="True">
<syntaxeditor:EditorDocument xml:space="preserve"><![CDATA[//Please connect to a ProtoPad-enabled app!]]></syntaxeditor:EditorDocument>
Expand All @@ -37,11 +40,11 @@

<GridSplitter ResizeDirection="Rows" Height="5" HorizontalAlignment="Stretch" VerticalAlignment="Bottom"/>
<DockPanel Grid.Row="1">
<WebBrowser x:Name="ResultTextBox" />
<WebBrowser x:Name="ResultTextBox" />
</DockPanel>
<StatusBar Height="30" Grid.Row="2" VerticalAlignment="Top">
<Label x:Name="StatusLabel"></Label>

</StatusBar>
</Grid>
</Grid>
</Window>
92 changes: 75 additions & 17 deletions ProtoPad Client/MainWindow.xaml.cs
Expand Up @@ -23,7 +23,6 @@ namespace ProtoPad_Client
{
public partial class MainWindow
{
public const string AndroidPort = "12345";
public const string CodeTemplateStatementsPlaceHolder = "__STATEMENTSHERE__";
public const int multicastForwardedHostPort = 15353; // todo: auto-find available port
public const int httpForwardedHostPort = 18080; // todo: auto-find available port
Expand Down Expand Up @@ -72,7 +71,6 @@ private async void SearchForRunningProtoPadServers()
ready => { },
(name, address) => Dispatcher.Invoke(() =>
{
if (address.Contains("?")) address = address.Replace("?", AndroidPort);
var deviceItem = new DeviceItem
{
DeviceAddress = address,
Expand All @@ -91,7 +89,7 @@ private async void SearchForRunningProtoPadServers()
}));

ResultTextBox.NavigateToString(Properties.Resources.ResultHtmlWrap);
await ResultTextBox.GetEventAsync<NavigationEventArgs>("Navigated");
await ResultTextBox.GetNavigatedEventAsync("Navigated");
var htmlDocument = ResultTextBox.Document as HTMLDocument;
_htmlHolder = htmlDocument.getElementById("wrapallthethings") as HTMLDivElementClass;
_htmlWindow = htmlDocument.parentWindow;
Expand Down Expand Up @@ -295,11 +293,10 @@ private void DevicesComboBox_SelectionChanged(object sender, System.Windows.Cont
SendCodeButton.IsEnabled = true;
LoadAssemblyButton.IsEnabled = true;


if (_currentDevice.DeviceType == DeviceTypes.Android) return;

var wrapText = EditorHelpers.GetWrapText(EditorHelpers.CodeType.Expression, _currentDevice.DeviceType);
var getFolderCode = wrapText.Replace("__STATEMENTSHERE__", _currentDevice.DeviceType == DeviceTypes.iOS
? "Environment.GetFolderPath(Environment.SpecialFolder.Personal)"
: ""); //todo: Android
var getFolderCode = wrapText.Replace("__STATEMENTSHERE__", "Environment.GetFolderPath(Environment.SpecialFolder.Personal)");
var result = SendCode(_currentDevice.DeviceAddress, false, getFolderCode);
if (result == null) return;
var folder = result.Results.FirstOrDefault();
Expand Down Expand Up @@ -361,39 +358,100 @@ private void AboutHelpButton_Click(object sender, RoutedEventArgs e)

private void AddManualIPButton_Click(object sender, RoutedEventArgs e)
{
var promptWindow = new PromptIPAddressWindow();
if (!promptWindow.ShowDialog().Value) return;
var address = String.Format("{0}:{1}", promptWindow.IPAddress, promptWindow.Port);
var deviceType = QuickConnect(address);
if (!deviceType.HasValue)
{
MessageBox.Show(String.Format("Could not connect to {0}", address));
return;
}
var deviceItem = new DeviceItem
{
DeviceAddress = address,
DeviceName = String.Format("{0} app on {1}", deviceType, address),
DeviceType = deviceType.Value
};
if (!DevicesComboBox.Items.Cast<object>().Any(i => (i as DeviceItem).DeviceAddress == deviceItem.DeviceAddress))
{
DevicesComboBox.Items.Add(deviceItem);
}
DevicesComboBox.IsEnabled = true;
}

private void ConfigureAndroidEmulatorButton_Click(object sender, RoutedEventArgs e)
{
var emulatorPortCandidates = FindEmulatorPortCandidates();
if (!emulatorPortCandidates.Any())
{
MessageBox.Show("No emulators found - are you sure the emulator is online?");
return;
}
var results = emulatorPortCandidates.ToDictionary(c => c, SetupPortForwardingOnAndroidEmulator);
var successResults = results.Where(r => r.Value.HasValue && r.Value.Value).Select(r => r.Key);
if (successResults.Any())
{
MessageBox.Show(String.Format("Emulator(s) found at port(s) {0}, and configured successfully! Hit 'Find servers' to auto-discover your running app on this/these emulator(s).", String.Join(", ", successResults)));
return;
}
var halfResults = results.Where(r => r.Value.HasValue && !r.Value.Value).Select(r => r.Key);
if (halfResults.Any())
{
MessageBox.Show(String.Format("Emulator(s) found at port(s) {0}, but they may have already been configured, or were not able to be configured successfully. Please retry auto-discovery.", String.Join(", ", halfResults)));
return;
}
var emptyResults = results.Where(r => !r.Value.HasValue).Select(r => r.Key);
MessageBox.Show(String.Format("Emulator(s) found at port(s) {0}, but they could not be telnet-connected to. Please retry auto-discovery.", String.Join(", ", emptyResults)));
}

/// <summary>
/// Tries to find and Telnet-connect to the (first) running Android Emulator (AVD)
/// And tries to set up port forwarding on it, so that the ProtoPad Http (command) and Udp (discovery) servers are accessible from your host machine.
/// </summary>
/// <returns>Whether the port forwarding setup was succesful (might fail if already set up or ports busy)</returns>
private bool SetupPortForwardingOnAndroidEmulator()
private static bool? SetupPortForwardingOnAndroidEmulator(int port = 5554)
{
var udpCommandResponse = "";
var tcpCommandResponse = "";
try
{
var tc = new TelnetConnection("localhost", 5554);
var connectResponse = tc.Read();
if (!connectResponse.Contains("Android Console")) return false;

tc.WriteLine(String.Format("redir add udp:{0}:{1}", multicastForwardedHostPort, UdpDiscoveryServer.UdpServerPort));
udpCommandResponse = tc.Read();
tc.WriteLine(String.Format("redir add tcp:{0}:{1}", httpForwardedHostPort, 8080));
tcpCommandResponse = tc.Read();
using (var tc = new TelnetConnection("localhost", port))
{
var connectResponse = tc.Read();
if (!connectResponse.Contains("Android Console")) return null;

tc.WriteLine(String.Format("redir add udp:{0}:{1}", multicastForwardedHostPort, UdpDiscoveryServer.UdpServerPort));
udpCommandResponse = tc.Read();
tc.WriteLine(String.Format("redir add tcp:{0}:{1}", httpForwardedHostPort, 8080));
tcpCommandResponse = tc.Read();
}
}
catch (Exception e)
{
Debug.WriteLine("Unexpected error during Android Emulator Telnet session: {0}", e.Message);
return false;
return null;
}

// response in case already set up:
// "KO: host port already active, use 'redir del' to remove first"

return udpCommandResponse.Contains("OK") && tcpCommandResponse.Contains("OK");
}

private static DeviceTypes? QuickConnect(string endpoint)
{
var appIdentifier = SimpleHttpServer.SendWhoAreYou(endpoint);
if (String.IsNullOrWhiteSpace(appIdentifier)) return null;
if (appIdentifier.Contains("Android")) return DeviceTypes.Android;
if (appIdentifier.Contains("iOS")) return DeviceTypes.iOS;
return null;
}

private static IEnumerable<int> FindEmulatorPortCandidates()
{
var tcpRows = ManagedIpHelper.GetExtendedTcpTable(true);
return (from tcpRow in tcpRows let process = Process.GetProcessById(tcpRow.ProcessId) where process != null where process.ProcessName.Contains("emulator-arm") select tcpRow.LocalEndPoint.Port).Distinct().ToList();
}
}
}
32 changes: 32 additions & 0 deletions ProtoPad Client/PromptIPAddressWindow.xaml
@@ -0,0 +1,32 @@
<Window x:Class="ProtoPad_Client.PromptIPAddressWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:src="clr-namespace:ProtoPad_Client"
Title="PromptIPAddressWindow" Height="128" Width="220">
<Grid>
<Grid.Resources>

<src:CheckFilledConverter x:Key="TestConverter" />

</Grid.Resources>

<Button Content="Cancel" HorizontalAlignment="Left" Margin="10,66,0,0" VerticalAlignment="Top" Width="75" IsCancel="True"/>
<Button Content="OK" HorizontalAlignment="Left" Margin="127,66,0,0" VerticalAlignment="Top" Width="75" IsDefault="True">
<Button.IsEnabled>

<MultiBinding Converter="{StaticResource TestConverter}">

<Binding ElementName="IPAddressTextBox" Path="Text" />

<Binding ElementName="PortTextBox" Path="Text" />

</MultiBinding>

</Button.IsEnabled>
</Button>
<TextBox x:Name="IPAddressTextBox" HorizontalAlignment="Left" Height="23" Margin="87,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="115"/>
<TextBox x:Name="PortTextBox" HorizontalAlignment="Left" Height="23" Margin="87,38,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="115"/>
<Label Content="IP address:" HorizontalAlignment="Left" Margin="10,6,0,0" VerticalAlignment="Top"/>
<Label Content="Port:" HorizontalAlignment="Left" Margin="10,34,0,0" VerticalAlignment="Top"/>

</Grid>
</Window>
44 changes: 44 additions & 0 deletions ProtoPad Client/PromptIPAddressWindow.xaml.cs
@@ -0,0 +1,44 @@
using System;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace ProtoPad_Client
{
public partial class PromptIPAddressWindow
{
public static readonly DependencyProperty IPAddressProperty = DependencyProperty.Register("Text", typeof(String), typeof(TextBox), new FrameworkPropertyMetadata(""));
public string IPAddress
{
get { return Convert.ToString(GetValue(IPAddressProperty)); }
set { SetValue(IPAddressProperty, value); }
}

public static readonly DependencyProperty PortProperty = DependencyProperty.Register("Text", typeof(String), typeof(TextBox), new FrameworkPropertyMetadata(""));
public int Port
{
get { return int.Parse(Convert.ToString(GetValue(PortProperty))); }
set { SetValue(PortProperty, value); }
}

public PromptIPAddressWindow()
{
InitializeComponent();
}
}

public class CheckFilledConverter: IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values.All(v => (v is string) && (!String.IsNullOrWhiteSpace(v.ToString())));
}

public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
7 changes: 7 additions & 0 deletions ProtoPad Client/ProtoPad Client.csproj
Expand Up @@ -89,6 +89,9 @@
<Compile Include="AboutWindow.xaml.cs">
<DependentUpon>AboutWindow.xaml</DependentUpon>
</Compile>
<Compile Include="PromptIPAddressWindow.xaml.cs">
<DependentUpon>PromptIPAddressWindow.xaml</DependentUpon>
</Compile>
<Compile Include="TelnetInterface.cs" />
<Page Include="AboutWindow.xaml">
<SubType>Designer</SubType>
Expand All @@ -110,6 +113,10 @@
<DependentUpon>MainWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Page Include="PromptIPAddressWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs">
Expand Down
10 changes: 9 additions & 1 deletion ProtoPad Client/TelnetInterface.cs
Expand Up @@ -192,7 +192,7 @@ enum Options
SGA = 3
}

class TelnetConnection
public class TelnetConnection: IDisposable
{
readonly TcpClient tcpSocket;

Expand Down Expand Up @@ -293,5 +293,13 @@ void ParseTelnet(StringBuilder sb)
}
}
}

public void Dispose()
{
if (tcpSocket != null)
{
tcpSocket.Close();
}
}
}
}

0 comments on commit 692d04e

Please sign in to comment.