/// <summary> /// When the user presses "Start Streaming Steam", first check that they are paired /// </summary> public static async Task<StreamContext> StartStreaming(CoreDispatcher uiDispatcher, Computer computer, MoonlightStreamConfiguration streamConfig) { PairingManager p = new PairingManager(computer); // If we can't get the pair state, return bool? pairState = await p.QueryPairState(); if (!pairState.HasValue) { DialogUtils.DisplayDialog(uiDispatcher, "Pair state query failed", "Failed to start streaming"); return null; } // If we're not paired, return if (pairState == false) { DialogUtils.DisplayDialog(uiDispatcher, "Device not paired", "Failed to start streaming"); return null; } // Lookup the desired app in the app list // NOTE: This will go away when we have a proper app list int appId = await LookupAppIdForApp(uiDispatcher, new NvHttp(computer.IpAddress), "Steam"); if (appId == 0) { // LookupAppIdForApp() handles displaying a failure dialog return null; } return new StreamContext(computer, appId, streamConfig); }
/// <summary> /// Pair with the hostname in the textbox /// </summary> public async Task Pair(CoreDispatcher uiDispatcher, Computer c) { Debug.WriteLine("Pairing..."); // Get the pair state. bool? pairState = await QueryPairState(); if (pairState == true) { DialogUtils.DisplayDialog(uiDispatcher, "This device is already paired to the host PC", "Already Paired"); return; } // pairstate = null. We've encountered an error else if (!pairState.HasValue) { DialogUtils.DisplayDialog(uiDispatcher, "Failed to query pair state", "Pairing failed"); return; } bool challenge = await PairingCryptoHelpers.PerformPairingHandshake(uiDispatcher, new WindowsCryptoProvider(), nv, nv.GetUniqueId()); if (!challenge) { Debug.WriteLine("Challenges failed"); return; } // Otherwise, everything was successful MainPage.SaveComputer(c); // FIXME: We can't have two dialogs open at once. // DialogUtils.DisplayDialog(uiDispatcher, "Pairing successful", "Success"); }
/// <summary> /// Event handler for clicking the add PC manually button /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Add_Button_Click(object sender, RoutedEventArgs e) { if (String.IsNullOrWhiteSpace(ip_textbox.Text) || String.IsNullOrWhiteSpace(nickname_textbox.Text)) { DialogUtils.DisplayDialog(this.Dispatcher, "Please fill out both text boxes", "Add PC Failed"); return; } else { Computer toAdd = new Computer(nickname_textbox.Text, ip_textbox.Text); this.Frame.Navigate(typeof(MainPage), toAdd); } }
public StreamContext(Computer computer, int appId, MoonlightStreamConfiguration streamConfig) { this.computer = computer; this.appId = appId; this.streamConfig = streamConfig; }
/// <summary> /// Uses mDNS to enumerate the machines on the network eligible to stream from /// </summary> /// <returns></returns> private async Task EnumerateEligibleMachines() { // Make a local copy of the computer list // The UI thread will populate the listbox with computerList whenever it pleases, so we don't want it to take the one we're modifying List<Computer> computerListLocal = new List<Computer>(computerList); bool modifiedList = false; // Make sure we have the manually added PCs in here foreach (var pc in addedPCs) { if (!computerListLocal.Exists(x => x.IpAddress == pc.IpAddress)) { computerListLocal.Add(pc); modifiedList = true; } } Debug.WriteLine("Enumerating machines..."); // If there's no network, save time and don't do the time-consuming mDNS if (!InternetAvailable) { if (computerListLocal.Count == 0 && !computerListLocal.Contains(noNetwork)) { computerListLocal.Add(noNetwork); modifiedList = true; } Debug.WriteLine("Network not available - skipping mDNS"); } else { // Remove the placeholder if (computerListLocal.Contains(noNetwork)) { Debug.WriteLine("Removing \"no network\" placeholder"); computerListLocal.Remove(noNetwork); modifiedList = true; } // Let Zeroconf do its magic and find everything it can with mDNS ILookup<string, string> domains = null; try { domains = await ZeroconfResolver.BrowseDomainsAsync(); } catch (Exception e) { Debug.WriteLine("Browse Domains Async threw exception: " + e.Message); } IReadOnlyList<IZeroconfHost> responses = null; if (domains != null) { try { responses = await ZeroconfResolver.ResolveAsync(domains.Select(g => g.Key)); } catch (Exception e) { Debug.WriteLine("Exception in ZeroconfResolver.ResolverAsyc (Expected if BrowseDomainsAsync excepted): " + e.Message); } } if (responses != null) { // Remove the "not found" placeholder if (computerList.Contains(notFound)) { Debug.WriteLine("Removing \"not found\" placeholder"); computerList.Remove(notFound); modifiedList = true; } // Go through every response we received and grab only the ones running nvstream foreach (var resp in responses) { if (resp.Services.ContainsKey("_nvstream._tcp.local.")) { Computer toAdd = new Computer(resp.DisplayName, resp.IPAddress); // If we don't have the computer already, add it if (!computerListLocal.Exists(x => x.IpAddress == resp.IPAddress)) { computerListLocal.Add(toAdd); Debug.WriteLine(resp); modifiedList = true; } } } } // We're done messing with the list - it's okay for the UI thread to update it now Computer last = LoadComputer(); if (last != null) { // If we don't have the computer already, add it if (!computerListLocal.Exists(x => x.IpAddress == last.IpAddress)) { modifiedList = true; computerListLocal.Add(last); } } // If no computers at all, say none are found. if (computerListLocal.Count == 0) { Debug.WriteLine("Not Found"); modifiedList = true; computerListLocal.Add(notFound); } } if (modifiedList) { computerList = computerListLocal; if (computerPicker.SelectedIndex == -1) { computerPicker.ItemsSource = computerList; } } }
/// <summary> /// Once we freshly pair to a computer, save it /// </summary> /// <param name="c">Computer we've paired to</param> public static void SaveComputer(Computer c) { var settings = ApplicationData.Current.RoamingSettings; settings.Values["computerName"] = c.Name; settings.Values["computerIP"] = c.IpAddress; }
/// <summary> /// Constructor that sets nv /// </summary> /// <param name="nv">The NvHttp Object</param> public PairingManager(Computer computer) { this.nv = new NvHttp(computer.IpAddress); }
/// <summary> /// Executed when the user presses "Start Streaming Steam!" /// </summary> private async Task StreamButton_Click_Common() { Debug.WriteLine("Start Streaming button pressed"); selected = (Computer)computerPicker.SelectedItem; // User hasn't selected a machine or selected a placeholder if (selected == null || String.IsNullOrWhiteSpace(selected.IpAddress)) { DialogUtils.DisplayDialog(this.Dispatcher, "No machine selected", "Streaming Failed"); } else { // Stop enumerating machines while we're trying to check pair state mDnsTimer.Stop(); byte[] aesKey = PairingCryptoHelpers.GenerateRandomBytes(16); // GameStream only uses 4 bytes of a 16 byte IV. Go figure. byte[] aesRiIndex = PairingCryptoHelpers.GenerateRandomBytes(4); byte[] aesIv = new byte[16]; Array.ConstrainedCopy(aesRiIndex, 0, aesIv, 0, aesRiIndex.Length); SettingsPage s = new SettingsPage(); MoonlightStreamConfiguration config = new MoonlightStreamConfiguration( s.GetStreamWidth(), s.GetStreamHeight(), s.GetStreamFps(), 10000, // FIXME: Scale by resolution 1024, aesKey, aesIv); StreamContext context = await ConnectionManager.StartStreaming(this.Dispatcher, selected, config); if (context != null) { this.Frame.Navigate(typeof(StreamFrame), context); } } }