Skip to content

Commit

Permalink
Added TapMode
Browse files Browse the repository at this point in the history
  • Loading branch information
uberhalit committed Feb 5, 2017
1 parent 0d0b467 commit 7060706
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 48 deletions.
3 changes: 3 additions & 0 deletions CitraTouchControl/App.config
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@
<setting name="KeyPressDuration" serializeAs="String">
<value>50</value>
</setting>
<setting name="IsTapOnly" serializeAs="String">
<value>False</value>
</setting>
</CitraTouchControl.Properties.Settings>
</userSettings>
</configuration>
1 change: 1 addition & 0 deletions CitraTouchControl/GlobalVars.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ class GlobalVars
internal static bool IsTouchEnabled = false;
internal static bool AreControlsHidden = false;
internal static int KeyPressDuration = 50;
internal static bool IsTapOnly = false;
}
}
63 changes: 28 additions & 35 deletions CitraTouchControl/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ private void Window_Loaded(object sender, RoutedEventArgs e)
GlobalVars.AreControlsHidden = true;
ToggleControls(true);
}
GlobalVars.IsTapOnly = Properties.Settings.Default.IsTapOnly;
GlobalVars.KeyPressDuration = Properties.Settings.Default.KeyPressDuration;
}

Expand All @@ -88,6 +89,9 @@ private void controlButton_TouchDown(object sender, TouchEventArgs e)
/// </summary>
private void controlButton_TouchUp(object sender, TouchEventArgs e)
{
// ignore TouchUp in TapOnly mode
if (GlobalVars.IsTapOnly)
return;
e.Handled = true;
ControlButtonUp(sender);
}
Expand All @@ -97,6 +101,9 @@ private void controlButton_TouchUp(object sender, TouchEventArgs e)
/// </summary>
private void controlButton_TouchLeave(object sender, TouchEventArgs e)
{
// ignore TouchLeave in TapOnly mode
if (GlobalVars.IsTapOnly)
return;
ControlButtonUp(sender);
}

Expand Down Expand Up @@ -140,16 +147,24 @@ private void ControlButtonDown(object sender)
// actually send keydown
SendMessage(citraMainControlHwnd, WM_KEYDOWN, key, IntPtr.Zero);

// add pressed key to list
if (!pressedControls.Contains(key))
pressedControls.Add(key);
// if TapMode is tap only then send KeyUp directly
if (GlobalVars.IsTapOnly)
{
SendButtonUp(key);
}
else
{
// add pressed key to list
if (!pressedControls.Contains(key))
pressedControls.Add(key);
}
}

/// <summary>
/// Control leave handler which supports asynchronous waiting.
/// </summary>
/// <param name="sender">The caller UI element.</param>
private async void ControlButtonUp(object sender)
private void ControlButtonUp(object sender)
{
// get calling button
var button = (sender as System.Windows.Controls.Image);
Expand All @@ -165,6 +180,15 @@ private async void ControlButtonUp(object sender)
return;
pressedControls.Remove(key);

SendButtonUp(key);
}

/// <summary>
/// Sends a KeyButtonUp after a short delay.
/// </summary>
/// <param name="key">The IntPtr of the key.</param>
private async void SendButtonUp(IntPtr key)
{
// wait 50ms asynchronously, then send keyup of selected button
await Task.Delay(GlobalVars.KeyPressDuration);
SendMessage(citraMainControlHwnd, WM_KEYUP, key, IntPtr.Zero);
Expand Down Expand Up @@ -332,37 +356,6 @@ private struct RECT
#region UNUSED

/*
private const int WS_EX_TRANSPARENT = 0x00000020;
private const int GWL_EXSTYLE = (-20);
[DllImport("user32.dll")]
private static extern Int32 GetWindowLong(IntPtr hwnd, int index);
[DllImport("user32.dll")]
private static extern Int32 SetWindowLong(IntPtr hwnd, int index, int newStyle);
public static void SetWindowExTransparent(IntPtr hwnd)
{
var extendedStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
SetWindowLong(hwnd, GWL_EXSTYLE, extendedStyle | WS_EX_TRANSPARENT);
}
public static void SetWindowExNormal(IntPtr hwnd)
{
var extendedStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
SetWindowLong(hwnd, GWL_EXSTYLE, extendedStyle & ~WS_EX_TRANSPARENT);
}
/// <summary>
/// Make window clickthrough and hittest invisible.
/// </summary>
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
var hwnd = new System.Windows.Interop.WindowInteropHelper(this).Handle;
SetWindowExTransparent(hwnd);
}
/// <summary>
/// Uses FindWindowEx() to recursively search for a child window with the given class and/or title.
/// </summary>
Expand Down
18 changes: 10 additions & 8 deletions CitraTouchControl/MenuWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:CitraTouchControl"
mc:Ignorable="d"
Title="CitraTouchControl - Menu" Height="450" Width="400" Topmost="True" WindowStartupLocation="CenterOwner" WindowStyle="ToolWindow" Background="LightGray" Loaded="Window_Loaded" Closing="Window_Closing">
Title="CitraTouchControl - Menu" Height="475" Width="400" Topmost="True" WindowStartupLocation="CenterOwner" WindowStyle="ToolWindow" Background="LightGray" Loaded="Window_Loaded" Closing="Window_Closing">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
Expand All @@ -20,15 +20,17 @@
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Button x:Name="bResize" Grid.Row="0" Content="Readjust Overlay" FontSize="24" FontWeight="Bold" Margin="10,5,10,5" BorderThickness="1" Focusable="False" Click="bResize_Click" Grid.ColumnSpan="3"/>
<Button x:Name="bTouch" Grid.Row="1" Content="Enable Touch" FontSize="24" FontWeight="Bold" Margin="10,5,10,5" BorderThickness="1" Focusable="False" Click="bTouch_Click" Grid.ColumnSpan="3"/>
<Button x:Name="bControls" Grid.Row="2" Content="Hide Controls" FontSize="24" FontWeight="Bold" Margin="10,5,10,5" BorderThickness="1" Focusable="False" Click="bControls_Click" Grid.ColumnSpan="3"/>
<Button x:Name="bKeys" Grid.Row="3" Content="Configurate Keys" FontSize="24" FontWeight="Bold" Margin="10,5,10,5" BorderThickness="1" Focusable="False" Click="bKeys_Click" Grid.ColumnSpan="3"/>
<Button x:Name="bResize" Grid.Row="0" Grid.Column="0" Content="Readjust Overlay" FontSize="24" FontWeight="Bold" Margin="10,5,10,5" BorderThickness="1" Focusable="False" Click="bResize_Click" Grid.ColumnSpan="3"/>
<Button x:Name="bTouch" Grid.Row="1" Grid.Column="0" Content="Enable Touch" FontSize="24" FontWeight="Bold" Margin="10,5,10,5" BorderThickness="1" Focusable="False" Click="bTouch_Click" Grid.ColumnSpan="3"/>
<Button x:Name="bControls" Grid.Row="2" Grid.Column="0" Content="Hide Controls" FontSize="24" FontWeight="Bold" Margin="10,5,10,5" BorderThickness="1" Focusable="False" Click="bControls_Click" Grid.ColumnSpan="3"/>
<Button x:Name="bTap" Grid.Row="3" Grid.Column="0" Content="Touch: Tap only" FontSize="24" FontWeight="Bold" Margin="10,5,10,5" BorderThickness="1" Focusable="False" Grid.ColumnSpan="3" Click="bTap_Click"/>
<Button x:Name="bDPlus" Grid.Row="4" Grid.Column="0" Content="+" FontSize="24" FontWeight="Bold" Margin="10,5,5,5" BorderThickness="1" Focusable="False" Click="bDPlus_Click"/>
<Button x:Name="bDuration" Grid.Row="4" Grid.Column="1" Content="KeyPress: 60ms" FontSize="24" FontWeight="Bold" Margin="5,5,5,5" BorderThickness="1" Focusable="False" />
<Button x:Name="bDuration" Grid.Row="4" Grid.Column="1" Content="KeyPress: 50ms" FontSize="24" FontWeight="Bold" Margin="5,5,5,5" BorderThickness="1" Focusable="False" />
<Button x:Name="bDMinus" Grid.Row="4" Grid.Column="2" Content="-" FontSize="24" FontWeight="Bold" Margin="5,5,10,5" BorderThickness="1" Focusable="False" Click="bDMinus_Click" />
<Button x:Name="bExit" Grid.Row="5" Content="Exit CitraTouchControl" FontSize="24" FontWeight="Bold" Foreground="DarkRed" Margin="10,5,10,5" BorderThickness="1" Focusable="False" Click="bExit_Click" Grid.ColumnSpan="3"/>
<Button x:Name="bClose" Grid.Row="6" Content="Close Window" FontSize="24" FontWeight="Bold" Margin="10,5,10,5" BorderThickness="1" Focusable="False" Click="bClose_Click" Grid.ColumnSpan="3"/>
<Button x:Name="bKeys" Grid.Row="5" Grid.Column="0" Content="Configurate Keys" FontSize="24" FontWeight="Bold" Margin="10,5,10,5" BorderThickness="1" Focusable="False" Click="bKeys_Click" Grid.ColumnSpan="3"/>
<Button x:Name="bExit" Grid.Row="6" Grid.Column="0" Content="Exit CitraTouchControl" FontSize="24" FontWeight="Bold" Foreground="DarkRed" Margin="10,5,10,5" BorderThickness="1" Focusable="False" Click="bExit_Click" Grid.ColumnSpan="3"/>
<Button x:Name="bClose" Grid.Row="7" Grid.Column="0" Content="Close Window" FontSize="24" FontWeight="Bold" Margin="10,5,10,5" BorderThickness="1" Focusable="False" Click="bClose_Click" Grid.ColumnSpan="3"/>
</Grid>
</Window>
31 changes: 26 additions & 5 deletions CitraTouchControl/MenuWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ private void Window_Loaded(object sender, RoutedEventArgs e)
bTouch.Content = "Disable Touch";
if (GlobalVars.AreControlsHidden)
bControls.Content = "Show Controls";
if (GlobalVars.IsTapOnly)
bTap.Content = "Touch: Tap & Hold";
bDuration.Content = "KeyPress: " + GlobalVars.KeyPressDuration + "ms";
}

Expand Down Expand Up @@ -86,13 +88,22 @@ private void bControls_Click(object sender, RoutedEventArgs e)
}

/// <summary>
/// Shows key binding window.
/// Toggle TapMode.
/// </summary>
private void bKeys_Click(object sender, RoutedEventArgs e)
private void bTap_Click(object sender, RoutedEventArgs e)
{
KeysWindow kW = new KeysWindow();
kW.Owner = this;
kW.Show();
if (!GlobalVars.IsTapOnly)
{
bTap.Content = "Touch: Tap & Hold";
Properties.Settings.Default.IsTapOnly = true;
GlobalVars.IsTapOnly = true;
}
else
{
bTap.Content = "Touch: Tap only";
Properties.Settings.Default.IsTapOnly = false;
GlobalVars.IsTapOnly = false;
}
}

/// <summary>
Expand All @@ -116,6 +127,16 @@ private void bDMinus_Click(object sender, RoutedEventArgs e)
bDuration.Content = "KeyPress: " + GlobalVars.KeyPressDuration + "ms";
}

/// <summary>
/// Shows key binding window.
/// </summary>
private void bKeys_Click(object sender, RoutedEventArgs e)
{
KeysWindow kW = new KeysWindow();
kW.Owner = this;
kW.Show();
}

/// <summary>
/// Exits the whole application.
/// </summary>
Expand Down
12 changes: 12 additions & 0 deletions CitraTouchControl/Properties/Settings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions CitraTouchControl/Properties/Settings.settings
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,8 @@
<Setting Name="KeyPressDuration" Type="System.Int32" Scope="User">
<Value Profile="(Default)">50</Value>
</Setting>
<Setting Name="IsTapOnly" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
</Settings>
</SettingsFile>
71 changes: 71 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# CitraTouchControl #

CitraTouchControl offers a multitouch able onscreen overlay for the [Nintendo 3DS Emulator Citra](https://github.com/citra-emu/citra).

This is useful for remote play sessions with Citra from a mobile device or if you just don't wan't to sit in front of your PC to play.

It will auto adjust to match Citra position and size, onscreen controls' keys can be modified and touch/controls can be toggled on and off.

![CitraTouchControl](https://i.imgur.com/u38o2VN.png)
## Requirements ##
* Citra-Qt build
* Windows PC which runs Citra at decent (>30) framerates
* Mobile device which can handle a massive input network-stream
* fast (>75Mbps) network connection between the host-PC and your mobile device (ideally PC->LAN->Router->5G WLAN->Mobile device)


## Usage ##
1. Start Citra and load up your game.
2. Connect to your host-PC via the Remote Desktop app on your mobile device.
3. Start CitraTouchControl via remote control.
4. Enjoy playing 3DS games on your tablet/phone.


## Configuration ##
1. [Enable RDP on your host PC](http://www.howtogeek.com/howto/windows-vista/turn-on-remote-desktop-in-windows-vista/)
* Make sure services "TermService" and "SessionEnv" are running if you encounter any problems
2. Start Citra and CitraTouchControl, press the menu button on CitraTouchControl (bottom one) to bring up the configuration menu
1. Press "Configurate Keys" to bring up the next window and set all your controls there
3. Install [Microsoft Remote Desktop Beta](https://play.google.com/store/apps/details?id=com.microsoft.rdc.android.beta) on your mobile device
4. Start it and add a new Desktop connection to your host PC
1. Insert the local IP and Windows credentials of your host PC
2. Enable "Custom display resolution" and set it to "788x1260"
3. If you want sound then set Sound to "Play sound on device" (this will require additional bandwidth)
![Microsoft Remote Desktop Beta](https://i.imgur.com/0RPYSDD.png)


## Options ##
* Readjust Overlay: will get position and size of Citra again and place overlay accordingly. Use this if the overlay is off.
* Enable Touch: will make the overlay "clickthrough" (buttons will still work)
* Hide controls: will remove all controls except the menu button from the overlay. Use this in combinition with enabled touch for a stylus like experience.
* Touch: Tap only: will make all onscreen buttons "static". If you press a button it will send KeyDown and KeyUp. Holding a button will not send multiple keystrokes anymore. Use this if you encounter involuntary KeyPresses (mostly due to lag).
* Keypress: adjust the delay a Key keeps beeing pressed after you removed your finger from the button. Reduce this if you encounter involuntary KeyPresses (mostly due to lag) or increase it if buttons doesn't get recognised anymore.
* Configurate Keys: set keys for all onscreen controls (only works with a physical keyboard, will not work over RDP from a mobile device)


## Tested Remote Desktop Apps ##
| Platorm | App | Working | Information |
| ------------- | ------------- | ------------- | ------------- |
| Android | [Microsoft Remote Desktop Beta](https://play.google.com/store/apps/details?id=com.microsoft.rdc.android.beta) | **_Yes_** |
| Android | [Microsoft Remote Desktop](https://play.google.com/store/apps/details?id=com.microsoft.rdc.android) | no | no portrait mode |
| Android | [TeamViewer](https://play.google.com/store/apps/details?id=com.teamviewer.teamviewer.market.mobile) | no | no portrait mode, distorted sound |
| Android | [RDP Remote Desktop aFreeRDP](https://play.google.com/store/apps/details?id=com.freerdp.afreerdp) | no | no (multi-)touch |
| Android | [aRDP Pro: Secure RDP Client](https://play.google.com/store/apps/details?id=com.iiordanov.aRDP) | no | no (multi-)touch |
| Android | [Remote Desktop Manager](https://play.google.com/store/apps/details?id=com.devolutions.remotedesktopmanager) | no | no (multi-)touch |
| Android | [Remotix VNC RDP Remote Desktop](https://play.google.com/store/apps/details?id=com.nulana.android.remotix) | no | no (multi-)touch, slow |
| Android | [NoMachine](https://play.google.com/store/apps/details?id=com.nomachine.nxplayer) | no | slow, no portrait mode |
| Android | [Remote Desktop Client](https://play.google.com/store/apps/details?id=com.xtralogic.android.rdpclient) | no | very slow |


## Contributing ##
If you find any Remote Desktop App which is fast enough for this kind of usage and supports portrait mode (also applies for other platforms like iOS or Windows Phone) then please let me know. Either send me a mail or create an issue here.


## Limitations ##
* Due to the fact that Microsofts RDP display driver does not support OpenGL 3.3 Citra will not work if you start it over a RDP-session. Start Citra and load your game **BEFORE** you connect via RDP.
* Configurating keys only works with a physical keyboard, it will not work via Remote Desktop from a mobile device.
* Overlay only checks for a proces called "citra-qt.exe". If your build of Citra is called something else then you'll have to rename it.
* Overlay will not adjust itself after startup. Use the "Readjust Overlay" option in menu if you move/resize Citra later.
* Overlay will not detect a restart of Citra. You'll have to manually restart CitraTouchControl if you close and reopen Citra.
* If Citra runs in admin mode you'll have to start CitraTouchControl as admin too.
* If your connection has lag >50ms CitraTouchControl will eventually hold KeyDown for too long which will result in recognition of multiple KeyPresses. Try the "Touch: Tap only" option in menu or reduce the KeyPress duration.

0 comments on commit 7060706

Please sign in to comment.