private static async Task LogExample() { // Set up our log handler to print logs to stdout ButtplugFFILog.LogMessage += (obj, msg) => { Console.WriteLine(msg); }; // Report everything at level Debug and higher, and since we're reporting to the // console, don't use JSON output. (JSON output is handy for log parsing later if // you need it.) ButtplugFFILog.StartLogHandler(ButtplugLogLevel.Debug, false); // If you want to change log levels without recompiling, you can use the Env Logger. // Just make sure you don't try to use StartLogHandler and ActivateEnvLogger in the // same session, they will conflict with each other and throw errors. // // To set the env logger filter level, you'll need to set the RUST_LOG environment // variable. i.e. in powershell: $env:RUST_LOG="debug" // // Comment the code above this and uncomment this if you want to try the env logger. // // ButtplugFFILog.ActivateEnvLogger(); // Completing our embedded connection should cause log messages to print. var connector = new ButtplugEmbeddedConnectorOptions(); var client = new ButtplugClient("Example Client"); await client.ConnectAsync(connector); }
private static async Task RunExample() { // After you've created a connector, the connection looks the same no // matter what, though the errors thrown may be different. var connector = new ButtplugEmbeddedConnectorOptions(); // If you'd like to try a remote network connection, comment the // connector line above and uncomment the one below. Note that you'll // need to turn off SSL on whatever server you're using. // var connector = new ButtplugWebsocketConnector( // new Uri("ws://localhost:12345/b******g")); var client = new ButtplugClient("Example Client"); // Now we connect. If anything goes wrong here, we'll either throw // // - A ButtplugClientConnectionException if there's a problem with // the Connector, like the network address being wrong, server not // being up, etc. // - A ButtplugHandshakeException if there is a client/server version // mismatch. try { await client.ConnectAsync(connector); } catch (ButtplugConnectorException ex) { // If our connection failed, because the server wasn't turned on, // SSL/TLS wasn't turned off, etc, we'll just print and exit // here. This will most likely be a wrapped exception. Console.WriteLine( $"Can't connect, exiting! Message: {ex.InnerException.Message}"); await WaitForKey(); return; } catch (ButtplugHandshakeException ex) { // This means our client is newer than our server, and we need to // upgrade the server we're connecting to. Console.WriteLine( $"Handshake issue, exiting! Message: {ex.InnerException.Message}"); await WaitForKey(); return; } // We're connected, yay! Console.WriteLine("Connected! Check Server for Client Name."); await WaitForKey(); // And now we disconnect as usual await client.DisconnectAsync(); }
private static async Task RunExample() { // Let's go back to our embedded connector now, to discuss what the // lifetime of a connection looks like. // // We'll create an embedded connector, but this time we're going to // include a maximum ping timeout of 100ms. This is a fairly short // timer, but since everything is running in our current process, it // should be fine. var connector = new ButtplugEmbeddedConnectorOptions(); connector.MaxPingTime = 100; var client = new ButtplugClient("Example Client"); // Just because the Client takes care of sending the ping message for // you doesn't mean that a connection is always perfect. It could be // that some code you write blocks the thread that the timer is // sending on, or sometimes the client's connection to the server can // be severed. In these cases, the client has events we can listen to // so we know when either we pinged out, or the server was disconnected. client.PingTimeout += (aObj, aEventArgs) => Console.WriteLine("B******g ping timeout!"); client.ServerDisconnect += (aObj, aEventArgs) => Console.WriteLine("B******g disconnected!"); // Let's go ahead and connect. await client.ConnectAsync(connector); Console.WriteLine("Client connected"); // If you'd like more information on what's going on, uncomment these 2 lines. // client.Log += (aObj, aMsg) => Console.WriteLine(aMsg.Message.LogMessage); // await client.RequestLogAsync(ButtplugLogLevel.Debug); // If we just sit here and wait, the client and server will happily // ping each other internally, so we shouldn't see anything printed // outside of the "hit key to continue" message. Our wait function is // async, so the event loop still spins and the timer stays happy. await WaitForKey(); // Now we'll kill the timer. You should see both a ping timeout and a // disconnected message from the event handlers we set up above. Console.WriteLine("Stopping ping timer"); await WaitForKey(); // At this point we should already be disconnected, so we'll just // show ourselves out. }
private void ConnectEmbedded() { // First off, we'll set up our Embedded Connector. var connector = new ButtplugEmbeddedConnectorOptions(); // If we want to change anything after making the options object, // we can just access the members. We'll explain more about this // in a later chapter. connector.ServerName = "New Server Name"; client = new ButtplugClient("Example Client"); // Connecting using an embedded connection should never fail. client.ConnectAsync(connector); }
private static async Task RunExample() { var connector = new ButtplugEmbeddedConnectorOptions(); var client = new ButtplugClient("Example Client"); try { await client.ConnectAsync(connector); } catch (Exception ex) { Console.WriteLine("Can't connect, exiting!"); Console.WriteLine($"Message: {ex.InnerException.Message}"); await WaitForKey(); return; } Console.WriteLine("Connected!"); // You usually shouldn't run Start/Stop scanning back-to-back like // this, but with TestDevice we know our device will be found when we // call StartScanning, so we can get away with it. await client.StartScanningAsync(); await client.StopScanningAsync(); Console.WriteLine("Client currently knows about these devices:"); foreach (var device in client.Devices) { Console.WriteLine($"- {device.Name}"); } await WaitForKey(); foreach (var device in client.Devices) { Console.WriteLine($"{device.Name} supports these messages:"); foreach (var msgInfo in device.AllowedMessages) { Console.WriteLine($"- {msgInfo.Key.ToString()}"); if (msgInfo.Value.FeatureCount != 0) { Console.WriteLine($" - Features: {msgInfo.Value.FeatureCount}"); } } } Console.WriteLine("Sending commands"); // Now that we know the message types for our connected device, we // can send a message over! Seeing as we want to stick with the // modern generic messages, we'll go with VibrateCmd. // // There's a couple of ways to send this message. var testClientDevice = client.Devices[0]; // We can use the convenience functions on ButtplugClientDevice to // send the message. This version sets all of the motors on a // vibrating device to the same speed. await testClientDevice.SendVibrateCmd(1.0); // If we wanted to just set one motor on and the other off, we could // try this version that uses an array. It'll throw an exception if // the array isn't the same size as the number of motors available as // denoted by FeatureCount, though. // // You can get the vibrator count using the following code, though we // know it's 2 so we don't really have to use it. // // This vibrateType variable is just used to keep us under 80 // characters for the dev guide, so don't feel that you have to reassign // types like this. I'm just trying to make it so you don't have to // horizontally scroll in the manual. :) var vibrateType = ServerMessage.Types.MessageAttributeType.VibrateCmd; var vibratorCount = testClientDevice.AllowedMessages[vibrateType].FeatureCount; await testClientDevice.SendVibrateCmd(new[] { 1.0, 0.0 }); await WaitForKey(); // And now we disconnect as usual. await client.DisconnectAsync(); // If we try to send a command to a device after the client has // disconnected, we'll get an exception thrown. try { await testClientDevice.SendVibrateCmd(1.0); } catch (ButtplugConnectorException e) { Console.WriteLine("Tried to send after disconnection! Exception: "); Console.WriteLine(e); } await WaitForKey(); }
private async void Connect_Click(object sender, RoutedEventArgs e) { Connect.IsEnabled = false; if (_client != null) { try { await _client.StopScanningAsync(); } catch (Exception) { // no-op: ignore failures, just stop if possible } await _client.DisconnectAsync(); _client = null; var devs = Devices.Keys; foreach (var dev in devs) { Devices.Remove(dev); } ButtplugConnType.IsEnabled = true; ButtplugConnType_SelectionChanged(this, null); Connect.Content = "Connect"; Connect.IsEnabled = true; Scan_Click(this, null); return; } try { switch (((ComboBoxItem)ButtplugConnType.SelectedValue).Content) { case "WebSocket": { ButtplugTarget.IsEnabled = false; ButtplugConnType.IsEnabled = false; ButtplugWebsocketConnectorOptions conn; try { conn = new ButtplugWebsocketConnectorOptions(new Uri(ButtplugTarget.Text)); } catch (UriFormatException e1) { MessageBox.Show($"Uri Error: {e1.Message}", "B******g Error", MessageBoxButton.OK, MessageBoxImage.Error); Connect.IsEnabled = true; ButtplugTarget.IsEnabled = true; ButtplugConnType_SelectionChanged(this, null); return; } _client = new ButtplugClient("NogasmChart"); _client.DeviceAdded += ClientOnDeviceAdded; _client.DeviceRemoved += ClientOnDeviceRemoved; _client.ServerDisconnect += ClientOnServerDisconnect; _client.ErrorReceived += ClientOnErrorReceived; _client.ScanningFinished += ClientOnScanFinished; await _client.ConnectAsync(conn); break; } case "Embedded": { ButtplugTarget.IsEnabled = false; ButtplugConnType.IsEnabled = false; ButtplugEmbeddedConnectorOptions conn = new ButtplugEmbeddedConnectorOptions(); conn.ServerName = "NogasmChart"; _client = new ButtplugClient("NogasmChart"); _client.DeviceAdded += ClientOnDeviceAdded; _client.DeviceRemoved += ClientOnDeviceRemoved; _client.ServerDisconnect += ClientOnServerDisconnect; _client.ErrorReceived += ClientOnErrorReceived; _client.ScanningFinished += ClientOnScanFinished; await _client.ConnectAsync(conn); break; } default: MessageBox.Show("Invalid Connection type!", "B******g Error", MessageBoxButton.OK, MessageBoxImage.Error); Connect.IsEnabled = true; return; } } catch (Exception e1) { MessageBox.Show($"Something went wrong: {e1.Message}", "B******g Error", MessageBoxButton.OK, MessageBoxImage.Error); try { if (_client != null) { await _client.DisconnectAsync(); } } catch (Exception) { // no-op: cleanup only } _client = null; ButtplugConnType.IsEnabled = true; ButtplugConnType_SelectionChanged(this, null); Connect.Content = "Connect"; Connect.IsEnabled = true; Scan_Click(this, null); return; } Connect.Content = "Disconnect"; Connect.IsEnabled = true; try { Scan.IsEnabled = false; await _client.StartScanningAsync(); Scan.Content = "Stop Scanning"; Scan.IsEnabled = true; } catch (Exception e1) { MessageBox.Show($"Something went wrong: {e1.Message}", "B******g Error", MessageBoxButton.OK, MessageBoxImage.Warning); } }
// Given a set of Client/Server options, creates a client that either // connects to a server process also started by us, or else to an external // server like Intiface Desktop. // // Throws if server is already running, or if server process fails to start // up, or if client fails to connect for some reason. public static async Task StartProcessAndCreateClient(ButtplugUnityClient client, ButtplugUnityOptions options) { if (options.OutputDebugMessages) { outputDebugMessages = true; } ButtplugUnityHelper.MaybeDebugLog($"Using connection type {options.ConnectorType}."); ButtplugUnityHelper.MaybeDebugLog("Bringing up B******g Server/Client"); // If the server is already up, we can't start it again, but we also can't // hand back a client. Throw. if (ButtplugUnityHelper.serverProcess != null) { ButtplugUnityHelper.MaybeDebugLog("Server already running, throwing"); throw new InvalidOperationException("Server already running."); } var websocketPort = options.WebsocketPort; // We want to start the CLI process without a window, and capture output // from stdout at the moment just to see if it's alive. Crude, but it // does the job of making sure we don't try to connect before the // process spins up. This will change to using the Intiface Protobuf // system in the future. if (options.ConnectorType == ButtplugUnityConnectorType.WebsocketServerProcess) { // If we aren't given a port to use, generate a random one. if (websocketPort == 0) { var rand = new System.Random(); websocketPort = (ushort)rand.Next(10000, 60000); } ButtplugUnityHelper.MaybeDebugLog($"Setting websocket port to {websocketPort}"); var processPath = Path.Combine(Application.streamingAssetsPath, "B******g", "IntifaceCLI.exe"); var arguments = $"--wsinsecureport {websocketPort} --pingtime {options.ServerPingTime}"; if (options.AllowRawMessages) { arguments += " --allowraw"; } try { // Create a new task that will resolve when the server comes up. ButtplugUnityHelper.serverBringupTask = new TaskCompletionSource <bool>(); var serverProcess = new Process(); serverProcess.StartInfo.FileName = processPath; // Don't open a window on starting, and make sure stdout is redirected // so we can catch it for bringup status. serverProcess.StartInfo.CreateNoWindow = true; serverProcess.StartInfo.RedirectStandardOutput = true; serverProcess.StartInfo.UseShellExecute = false; serverProcess.StartInfo.Arguments = arguments; ButtplugUnityHelper.MaybeDebugLog($"Starting task with arguments: {serverProcess.StartInfo.Arguments}"); serverProcess.Exited += ButtplugUnityHelper.OnServerExit; serverProcess.OutputDataReceived += ButtplugUnityHelper.OnServerStart; ButtplugUnityHelper.serverProcess = serverProcess; serverProcess.Start(); serverProcess.BeginOutputReadLine(); ButtplugUnityHelper.MaybeDebugLog("Waiting for task output"); // Wait to get something from the process await ButtplugUnityHelper.serverBringupTask.Task; ButtplugUnityHelper.MaybeDebugLog("Task output received, continuing"); // Reset our bringup task to null now that the process is either up or dead. ButtplugUnityHelper.serverBringupTask = null; if (ButtplugUnityHelper.serverProcess == null) { ButtplugUnityHelper.MaybeDebugLog("Process died before bringup finished."); throw new ApplicationException("ButtplugUnityHelper: Intiface process exited or crashed while coming up."); } } catch (Win32Exception processException) { ButtplugUnityHelper.MaybeDebugLog("Got process exception. If this is IL2CPP, this is expected and Ok. Printing exception and retrying using kernel32 P/Invoke methods."); ButtplugUnityHelper.MaybeDebugLog(processException.ToString()); // This might be a IL2CPP issue, in which case, try to launch the process using those bindings. // // The option here is to hide the window, so we'll flip the context from our option. StartExternalProcess.Start(processPath + " " + arguments, ".", !options.OpenIL2CPPConsoleWindow); } } // For some reason, in Unity 2018/2019 IL2CPP, awaiting our connect call // causes copies internally that end up dropping our sorter, meaning we'll // never get our connection confirmation back. This work everywhere in // Mono, and is fixed in IL2CPP in Unity 2020. // // Why am I doing this work for free. // // Logic tests for "202", meaning this should work back through 2018/2019, // but will futureproof us until Unity 2030 (or until they change their // major versioning scheme again). ButtplugUnityHelper.MaybeDebugLog("Connecting client"); if (options.ConnectorType == ButtplugUnityConnectorType.ExternalWebsocketServer || options.ConnectorType == ButtplugUnityConnectorType.WebsocketServerProcess) { var connector_options = new ButtplugWebsocketConnectorOptions(new Uri($"ws://{options.WebsocketAddress}:{websocketPort}/b******g")); if (Application.unityVersion.Contains("202")) { await client.ConnectAsync(connector_options); } else { client.ConnectAsync(connector_options); await Task.Delay(3000); } } else { var connector_options = new ButtplugEmbeddedConnectorOptions(); connector_options.AllowRawMessages = options.AllowRawMessages; connector_options.MaxPingTime = options.ServerPingTime; if (Application.unityVersion.Contains("202")) { await client.ConnectAsync(connector_options); } else { client.ConnectAsync(connector_options); await Task.Delay(3000); } } ButtplugUnityHelper.MaybeDebugLog("Connected client"); }