using System; using System.Linq; using System.Runtime.InteropServices; using System.Text.RegularExpressions; using System.Threading; namespace ParsecDisplay { internal static class CLI { static IntPtr VddHandle = IntPtr.Zero; static void ShowHelp() { Console.WriteLine("vdd command [args...]"); Console.WriteLine(" -a|add - Add a virtual display"); Console.WriteLine(" -r|remove - Remove the last added virtual display"); Console.WriteLine(" X - Remove the virtual display at index X (number)"); Console.WriteLine(" all - Remove all the added virtual displays"); Console.WriteLine(" -l|list - Show all the added virtual displays and specs"); Console.WriteLine(" -s|set X WxH - Set resolution for a virtual display"); Console.WriteLine(" where X is index number, WxH is size, e.g 1920x1080"); Console.WriteLine(" X @R - Set only the refresh rate R, e.g @60, @120 (hz)"); Console.WriteLine(" on Powershell, you should replace '@' with 'r'"); Console.WriteLine(" X WxH@R - Set full display mode as above, e.g 1920x1080@144"); Console.WriteLine(" -v|version - Query driver version and status"); Console.WriteLine(" -h|help - Show this help"); } public static int Execute(string[] args) { AttachConsole(-1); if (args.Length > 0) { try { switch (args[0]) { case "-a": case "add": return AddDisplay(); case "-r": case "remove": return RemoveDisplay(args); case "-l": case "list": return ListDisplay(); case "-s": case "set": return SetDisplayMode(args); case "-v": case "version": return QueryVersion(); case "-h": case "help": ShowHelp(); return 0; default: Console.WriteLine("Invalid command '{0}'", args[0]); ShowHelp(); return 0; } } catch (Exception ex) { Console.WriteLine("Error: {0}", ex.Message); #if DEBUG Console.Error.WriteLine(ex.StackTrace); #endif return -1; } finally { Vdd.Core.CloseHandle(VddHandle); } } else { ShowHelp(); return 0; } } static Device.Status PrepareVdd() { var status = Vdd.Core.QueryStatus(out var _); if (status == Device.Status.NOT_INSTALLED) { throw new Exception("The driver is not found, please install it first"); } else if (status != Device.Status.OK) { throw new Exception($"The driver is not OK, got status {status}"); } if (!Vdd.Core.OpenHandle(out VddHandle)) { throw new Exception("Failed to obtain the driver device handle"); } return status; } static void CheckAppRunning() { if (!EventWaitHandle.TryOpenExisting(Program.AppId, out var _)) { throw new Exception($"{Program.AppName} app is not running"); } } static int AddDisplay() { var displays = Vdd.Core.GetDisplays(); int maxCount = Vdd.Core.MAX_DISPLAYS; if (displays.Count >= maxCount) { throw new Exception(string.Format("Exceeded limit ({0}), could not add more displays", maxCount)); } PrepareVdd(); CheckAppRunning(); if (Vdd.Core.AddDisplay(VddHandle, out int index)) { Console.WriteLine($"Added a virtual display with index {index}."); return index; } else { throw new Exception("Failed to add a virtual display."); } } static int RemoveDisplay(string[] args) { var arg1 = args.Length >= 2 ? args[1] : ""; bool removeAll = arg1 == "all" || arg1 == "*"; int index = -1; if (args.Length == 1 || removeAll || int.TryParse(arg1, out index)) { var displays = Vdd.Core.GetDisplays(); if (displays.Count == 0) { Console.WriteLine("No Parsec Display available."); return 0; } else if (removeAll) { PrepareVdd(); // Reverse order preserves the Windows 10 Connectivity // registry config (see docs/issues/windows-10-display- // configurations-bug.md). Best-effort per display so // one failure doesn't strand the rest. int failed = 0; for (int i = displays.Count - 1; i >= 0; i--) { var di = displays[i]; if (!Vdd.Core.RemoveDisplay(VddHandle, di.DisplayIndex)) { failed++; Console.WriteLine("Warning: failed to remove display at index {0}.", di.DisplayIndex); } } if (failed > 0) { Console.WriteLine("Removed {0}/{1} displays.", displays.Count - failed, displays.Count); return 1; } Console.WriteLine("Removed all added displays."); return 0; } else { var display = index < 0 ? displays.LastOrDefault() : displays.FirstOrDefault(di => di.DisplayIndex == index); if (display != null) { PrepareVdd(); if (!Vdd.Core.RemoveDisplay(VddHandle, display.DisplayIndex)) throw new Exception(string.Format("Failed to remove the display at index {0}.", display.DisplayIndex)); Console.WriteLine("Removed display at index {0}.", display.DisplayIndex); return 0; } else { throw new Exception(string.Format("Display index {0} is not found.", index)); } } } else { throw new Exception(string.Format("Invalid display index '{0}'.", arg1)); } } static int ListDisplay() { var displays = Vdd.Core.GetDisplays(); if (displays.Count > 0) { foreach (var di in displays) { Console.WriteLine("Index: {0}", di.DisplayIndex); Console.WriteLine(" - Device: {0}", di.DeviceName); Console.WriteLine(" - Number: {0}", di.Identifier); Console.WriteLine(" - Name: {0}", di.DisplayName); Console.WriteLine(" - Mode: {0}", di.CurrentMode); Console.WriteLine(" - Orientation: {0} ({1}°)", di.CurrentOrientation, (int)di.CurrentOrientation * 90); } } else { Console.WriteLine("No virtual displays present."); } return displays.Count; } static int SetDisplayMode(string[] args) { if (args.Length < 2) throw new Exception("Missing display index."); if (args.Length < 3) throw new Exception("Missing resolution and/or refresh rate."); var argIndex = args[1]; var argDMode = string.Join(" ", args.Skip(2)); if (int.TryParse(argIndex, out int index)) { var displays = Vdd.Core.GetDisplays(); if (displays.Count == 0) { Console.WriteLine("No Parsec Display available."); return 0; } else { var display = displays.Find(di => di.DisplayIndex == index); if (display == null) throw new Exception(string.Format("Display index {0} is not found.", index)); int? width, height, hz; ParseDisplayModeArg(argDMode, out width, out height, out hz); if ((width != null && height != null) || hz != null) { if (display.ChangeMode(width, height, hz, null)) { Console.WriteLine($"Display index {index} is set to '{argDMode}'."); return 0; } else { throw new Exception($"Failed to set, display mode '{argDMode}' is not supported."); } } else { throw new Exception("Nothing to do, recheck your syntax."); } } } else { throw new Exception(string.Format("Invalid display index '{0}'.", argIndex)); } } static int QueryVersion() { var status = Vdd.Core.QueryStatus(out var version); Console.WriteLine(Vdd.Core.ADAPTER); Console.WriteLine("- Status: {0}", status); Console.WriteLine("- Version: {0}.{1}", version.Major, version.Minor); return (int)status; } static void ParseDisplayModeArg(string arg, out int? width, out int? height, out int? hz) { Match match; arg = arg.Trim(); width = null; height = null; hz = null; const string regexSize = @"^(\d+)\s*[xX]\s*(\d+)$"; if (Regex.IsMatch(arg, regexSize)) { match = Regex.Match(arg, regexSize); width = int.Parse(match.Groups[1].Value); height = int.Parse(match.Groups[2].Value); return; } const string regexHz = @"^[r@](\d+)$"; if (Regex.IsMatch(arg, regexHz)) { match = Regex.Match(arg, regexHz); hz = int.Parse(match.Groups[1].Value); return; } const string regexAll = @"^(\d+)\s*[xX]\s*(\d+)\s*[r@](\d+)$"; if (Regex.IsMatch(arg, regexAll)) { match = Regex.Match(arg, regexAll); width = int.Parse(match.Groups[1].Value); height = int.Parse(match.Groups[2].Value); hz = int.Parse(match.Groups[3].Value); return; } } [DllImport("kernel32.dll")] [return: MarshalAs(UnmanagedType.Bool)] static extern bool AttachConsole(int dwProcessId); } }