diff --git a/.gitignore b/.gitignore index 378a352..ed15b76 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ /TabletDriverGUI/obj/ /TabletDriverService/Release/ /TabletDriverService/Debug/ - +/TabletDriverService/startuplog.txt +TabletDriverService/config/usersettings.cfg diff --git a/README.md b/README.md index 1a84096..a59d4b4 100644 --- a/README.md +++ b/README.md @@ -2,177 +2,6 @@ This is a low latency graphics tablet driver that is meant to be used with rhythm game [osu!](https://osu.ppy.sh/home) -Currently the driver only works when the TabletDriverGUI is running. - -The GUI minimizes to system tray / notification area. You can reopen the GUI by double clicking the system tray icon. - -**If you have problems with the driver, please read the FAQ:** - -**https://github.com/hawku/TabletDriver/wiki/FAQ** - -## Download - -### http://hwk.fi/TabletDriver/TabletDriverV0.1.1.zip - -# - -### Supported operating systems: - - Windows 7 64-bit - - Windows 8 64-bit - - Windows 8.1 64-bit - - Windows 10 64-bit - -# - -### Supported tablets: - - Wacom CTL-470 - - Wacom CTL-471 - - Wacom CTL-472 - - Wacom CTL-480 - - Wacom CTH-480 - - Wacom CTL-490 - - XP Pen G430 (New 2016-2017 "Model B") - - XP Pen G640 - - Huion 420 - - Huion H640P - - Gaomon S56K - -### Configured, but not properly tested: - - Huion H420 - - Wacom CTL-4100 USB - - Wacom CTL-4100 Bluetooth - - Wacom CTH-470 - - Wacom CTH-670 - - Wacom CTL-671 - - Wacom CTL-672 - - Wacom CTL-680 - - Wacom CTH-680 - - Wacom CTH-490 - - Wacom PTH-451 - -# - -## Installation - -1. You might need to install these libraries, but usually these are already installed: - * https://www.microsoft.com/net/download/dotnet-framework-runtime - * https://aka.ms/vs/15/release/vc_redist.x86.exe - -2. Unzip the driver to a folder (Shorter path is recommended, for example `C:\Temp\TabletDriver`) -3. Uninstall all other tablet drivers. -4. Run `install_vmulti_driver.bat`. It might need a restart if there is another vmulti driver installed. -5. If you have Huion or Gaomon tablet, you need to run `install_huion_64.bat`, which is in the `driver_huion` directory. -6. Start the TabletDriverGUI.exe - -## Updating to a new version - -1. Unzip the new version -2. Start the TabletDriverGUI.exe - -## Uninstallation - -1. Uncheck the "Run at Windows startup" option in the GUI. -2. Run `remove_vmulti_driver.bat` -3. Run `remove_huion_64.bat`, which is in the `driver_huion` directory. - -# - -## VMulti and Huion driver binaries - -If you want to compile the code and don't want to install anything from the TabletDriver binary package, you will need extract the missing drivers from these installation packages: - -**VMulti driver:** -- https://www.xp-pen.com/upload/download/20180130/XP-PenWin(20180130).zip - -**Huion WinUSB driver:** -- https://www.huiontablet.com/drivers/WinDriver/HuionTablet_WinDriver_v13.15.16.zip - -# - -## Changelog - ->**v0.1.1:** -> - Added support for Wacom CTL-4100 (USB and Bluetooth) -> - Added settings import / export to the main menu. -> - Added Wacom backup reader to the Wacom area tool. -> - Added tablet benchmark tools to the console output context menu (Right click). -> - Moved the `config.xml` to the `config` folder. -> - Added noise reduction filter (`Noise` command, not in the GUI) -> - Code refactoring - ->**v0.1.0:** -> - Added `Bench` / `Benchmark` command. -> - Added `-hide` GUI command line parameter. GUI will start as minimized when you run `TabletDriverGUI.exe -hide` -> - Added an option to run the TabletDriverGUI at Windows startup. - ->**v0.0.18:** -> - Added TabletDriverService.exe multi-instance prevention. -> - Added yet another Wacom 490 tip click fix. -> `KeepTipDown` command sets how long the pen tip button output should be kept down after the pen tip is released. - ->**v0.0.17:** -> - Fixed driver crashing when used with the Huion or Gaomon tablets. - ->**v0.0.16:** -> - Added smoothing filter rate selector. Use a lower rate if you have filter performance problems. -> - Added TabletDriverService.exe process priority warning when the priority isn't set to High. -> - Desktop size settings are now available to everyone. -> Previously shown only when the developer mode was enabled. -> Automatic size should be used, but if you have problems with the screen mapping: -> https://github.com/hawku/TabletDriver/issues/4 -> - First few tablet position packets are now ignored to prevent the cursor jumping to a wrong position when driver is started. - ->**v0.0.15:** -> - Added more debug information to startuplog.txt. It now includes a list of connected input devices. -> - Added debug tools to the console output context menu (Right click). -> - Removed ClickPressure workaround from Wacom 490. -> You can re-enable that by adding `ClickPressure 500` to Commands tab. -> - Updated the wacom.cfg - ->**v0.0.14:** -> - Fix for the console "Copy all" function. - ->**v0.0.13:** -> - Added a direction indicator to the tablet area and moved the aspect ratio text to the middle of the area. -> - TabletDriverService process priority class is now set to "High" (suggestion by /u/Devocub) -> - TabletDriverService stability and error handling improvements (thanks to https://github.com/freakode) -> - New tablet configurations: Huion H420 (made by /u/uyghti) -> - Automatically generated Wacom tablet configurations (config\wacom.cfg) - ->**v0.0.12:** -> - Added multi-instance prevention. Old TabletDriverGUI.exe should pop up if you try to open another one. -> - New tablet configurations: Wacom CTL-680 and CTH-680 - ->**v0.0.11:** -> - Fix for DPI scaling problems. Screen mapping were wrong when the monitor DPI scaling wasn't 100% -> - Added a Wacom area tool. It should work with Wacom Intuos and Bamboo tablets (470->490) -> - Added startup debug log - ->**v0.0.10:** -> - New tablet configurations: Wacom CTH-470, CTH-670, PTH-451 -> - Fix for the smoothing filter. The filter didn't turn on when the settings were applied. -> - Fix for the Huion H640P clicking problem and also added better data validation for Huion 420, -> Gaomon S56K, XP Pen G430 and G640. -> - Modified click detection on CTL-490 and CTH-490 (tablet.cfg ClickPressure). - ->**v0.0.9:** -> - Yet another fix for the clicking problem... Maybe this time it will work? - ->**v0.0.8:** -> - Another fix for pen tip clicking. Improved the tablet data validation. - ->**v0.0.7:** -> - Added aspect ratio text to screen and tablet area. -> - Workaround for pen tip click detection. Some tablets don't send correct button data, so the pen tip click is now detected from the pressure data - ->**v0.0.6:** -> - Improved smoothing filter latency calculation - ->**v0.0.5:** -> - Added Windows Ink mode with pressure sensitivity -> - Added relative mouse mode -> - Added tablet area rotation -> - Added optional smoothing filter - ->**v0.0.4:** -> - Fixed a number conversion bug in the tablet area detection. +### This driver has not been updated for latest Windows versions! +#### Please, use OpenTabletDriver: +https://github.com/OpenTabletDriver/OpenTabletDriver diff --git a/TabletDriver.sln b/TabletDriver.sln index 0b37878..f63dc75 100644 --- a/TabletDriver.sln +++ b/TabletDriver.sln @@ -15,33 +15,23 @@ EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {6793EBBD-0EC4-4254-9573-A19ECA79F2C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6793EBBD-0EC4-4254-9573-A19ECA79F2C8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6793EBBD-0EC4-4254-9573-A19ECA79F2C8}.Debug|x64.ActiveCfg = Debug|Any CPU - {6793EBBD-0EC4-4254-9573-A19ECA79F2C8}.Debug|x64.Build.0 = Debug|Any CPU {6793EBBD-0EC4-4254-9573-A19ECA79F2C8}.Debug|x86.ActiveCfg = Debug|Any CPU {6793EBBD-0EC4-4254-9573-A19ECA79F2C8}.Debug|x86.Build.0 = Debug|Any CPU {6793EBBD-0EC4-4254-9573-A19ECA79F2C8}.Release|Any CPU.ActiveCfg = Release|Any CPU {6793EBBD-0EC4-4254-9573-A19ECA79F2C8}.Release|Any CPU.Build.0 = Release|Any CPU - {6793EBBD-0EC4-4254-9573-A19ECA79F2C8}.Release|x64.ActiveCfg = Release|Any CPU - {6793EBBD-0EC4-4254-9573-A19ECA79F2C8}.Release|x64.Build.0 = Release|Any CPU {6793EBBD-0EC4-4254-9573-A19ECA79F2C8}.Release|x86.ActiveCfg = Release|Any CPU {6793EBBD-0EC4-4254-9573-A19ECA79F2C8}.Release|x86.Build.0 = Release|Any CPU {3101CEC2-8F39-45FD-943B-79A488AD05EA}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {3101CEC2-8F39-45FD-943B-79A488AD05EA}.Debug|x64.ActiveCfg = Debug|x64 - {3101CEC2-8F39-45FD-943B-79A488AD05EA}.Debug|x64.Build.0 = Debug|x64 {3101CEC2-8F39-45FD-943B-79A488AD05EA}.Debug|x86.ActiveCfg = Debug|Win32 {3101CEC2-8F39-45FD-943B-79A488AD05EA}.Debug|x86.Build.0 = Debug|Win32 {3101CEC2-8F39-45FD-943B-79A488AD05EA}.Release|Any CPU.ActiveCfg = Release|Win32 - {3101CEC2-8F39-45FD-943B-79A488AD05EA}.Release|x64.ActiveCfg = Release|x64 - {3101CEC2-8F39-45FD-943B-79A488AD05EA}.Release|x64.Build.0 = Release|x64 {3101CEC2-8F39-45FD-943B-79A488AD05EA}.Release|x86.ActiveCfg = Release|Win32 {3101CEC2-8F39-45FD-943B-79A488AD05EA}.Release|x86.Build.0 = Release|Win32 EndGlobalSection diff --git a/TabletDriverGUI/App.xaml.cs b/TabletDriverGUI/App.xaml.cs index 9a061a2..14b7892 100644 --- a/TabletDriverGUI/App.xaml.cs +++ b/TabletDriverGUI/App.xaml.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.InteropServices; @@ -49,18 +50,93 @@ public App() } } + // + // Other tablet driver processes + // + string[] tabletDriverProcessNames = + { + + // Wacom + "Pen_Tablet", + "Wacom_Tablet", + + // XP-Pen + "PentabletService", + "Pentablet", + + // VEIKK + "TabletDriverCenter", + "TabletDriverSetting", + + // Huion + "Huion Tablet" + + }; + + + // + // Find driver processes + // + processes = Process.GetProcesses(); + List foundProcesses = new List(); + foreach (Process process in processes) + { + foreach (string processName in tabletDriverProcessNames) + { + if (process.ProcessName.ToLower() == processName.ToLower()) + { + foundProcesses.Add(process); + } + } + } + + // + // Try to kill driver processes + // + foreach (Process process in foundProcesses) + { + try + { + process.Kill(); + Thread.Sleep(100); + } + catch (Exception) + { + string processNames = ""; + foreach (Process p in foundProcesses) + { + processNames += "- " + p.ProcessName + ".exe\n "; + } + + MessageBox.Show( + "You have other driver processes running:\n " + + processNames + + "\nPlease shutdown those before starting this driver!", + "TabletDriverGUI - Error!", MessageBoxButton.OK, MessageBoxImage.Error + ); + instanceMutex.ReleaseMutex(); + Shutdown(); + return; + } + } + + MainWindow mainWindow = new MainWindow(); mainWindow.Show(); Exit += App_Exit; } else { + MessageBox.Show("TabletDriverGUI is already open!"); + // Broadcast to the another instance to show itself NativeMethods.PostMessage( - (IntPtr)NativeMethods.HWND_BROADCAST, - NativeMethods.WM_SHOWTABLETDRIVERGUI, - IntPtr.Zero, - IntPtr.Zero); + (IntPtr)NativeMethods.HWND_BROADCAST, + NativeMethods.WM_SHOWTABLETDRIVERGUI, + IntPtr.Zero, + IntPtr.Zero + ); + Shutdown(); } } diff --git a/TabletDriverGUI/Area.cs b/TabletDriverGUI/Area.cs index cccb7d4..d2ed205 100644 --- a/TabletDriverGUI/Area.cs +++ b/TabletDriverGUI/Area.cs @@ -43,20 +43,29 @@ public double Rotation { double angle; _rotation = value; + angle = _rotation * Math.PI / 180; + _rotationMatrix[0] = Math.Cos(angle); _rotationMatrix[1] = Math.Sin(angle); _rotationMatrix[2] = -Math.Sin(angle); _rotationMatrix[3] = Math.Cos(angle); + UpdateCorners(); } } + public bool IsEnabled; + + private double _rotation; - private double[] _rotationMatrix; + private readonly double[] _rotationMatrix; + private readonly Point[] _corners; - private Point[] _corners; + // + // Constructors + // public Area() { _corners = new Point[4] { @@ -67,11 +76,11 @@ public Area() }; _rotation = 0; _rotationMatrix = new double[4] { 1, 0, 0, 1 }; - _width = 0; _height = 0; X = 0; Y = 0; + IsEnabled = false; } public Area(double width, double height, double x, double y) : this() @@ -80,10 +89,26 @@ public Area(double width, double height, double x, double y) : this() _height = height; X = x; Y = y; + IsEnabled = false; UpdateCorners(); } + // + // Copy values from an another area + // + public void Set(Area area) + { + X = area.X; + Y = area.Y; + Width = area.Width; + Height = area.Height; + Rotation = area.Rotation; + IsEnabled = area.IsEnabled; + } + // + // Update corner positions + // private void UpdateCorners() { GetRotatedPoint(ref _corners[0], -_width / 2.0, -_height / 2.0); @@ -92,6 +117,9 @@ private void UpdateCorners() GetRotatedPoint(ref _corners[3], -_width / 2.0, _height / 2.0); } + // + // Rotate point + // public void GetRotatedPoint(ref Point p, double x, double y) { p.X = x * _rotationMatrix[0] + y * _rotationMatrix[1]; @@ -99,13 +127,44 @@ public void GetRotatedPoint(ref Point p, double x, double y) } // + // Rotate point in reverse direction // + public void GetRotatedPointReverse(ref Point p, double x, double y) + { + p.X = x * _rotationMatrix[3] - y * _rotationMatrix[1]; + p.Y = x * -_rotationMatrix[2] + y * _rotationMatrix[0]; + } + + + // + // Check if a point is inside of the area // - public Point[] Corners + public bool IsInside(Point p) { - get { return _corners; } + double x1, y1, x2, y2; + + x1 = p.X - X; + y1 = p.Y - Y; + x2 = x1 * _rotationMatrix[0] + y1 * _rotationMatrix[1]; + y2 = x1 * _rotationMatrix[2] + y1 * _rotationMatrix[3]; + + if ( + x2 > -Width / 2.0 && + x2 < +Width / 2.0 && + y2 > -Height / 2.0 && + y2 < +Height / 2.0 + ) + { + return true; + } + + return false; } + // + // Get corners + // + public Point[] Corners => _corners; // // Bounding Box @@ -128,6 +187,7 @@ public double[] GetBoundingBox() return box; } + // // Scale to fit inside another area // @@ -146,6 +206,9 @@ public void ScaleInside(Area target) UpdateCorners(); } + // + // Move area position inside of another area + // public void MoveInside(Area target) { double[] box = GetBoundingBox(); @@ -161,5 +224,20 @@ public void MoveInside(Area target) Y = target.Y + targetBox[3] - box[3]; } + + // + // Convert to string + // + public override string ToString() + { + return + "Area[" + + Utils.GetNumberString(X) + "," + + Utils.GetNumberString(Y) + "," + + Utils.GetNumberString(Width) + "," + + Utils.GetNumberString(Height) + + "]"; + } + } } diff --git a/TabletDriverGUI/Configuration.cs b/TabletDriverGUI/Configuration.cs index bd98f71..25bd26b 100644 --- a/TabletDriverGUI/Configuration.cs +++ b/TabletDriverGUI/Configuration.cs @@ -1,7 +1,6 @@ using System; -using System.Linq; -using System.Collections; using System.IO; +using System.Windows; using System.Xml; using System.Xml.Serialization; @@ -11,91 +10,305 @@ namespace TabletDriverGUI public class Configuration { public int ConfigVersion; + + public string TabletName; + + public Area ScreenArea; + [XmlArray("ScreenAreas")] + [XmlArrayItem("ScreenArea")] + public Area[] ScreenAreas; + public Area SelectedScreenArea; + public Area TabletArea; + [XmlArray("TabletAreas")] + [XmlArrayItem("TabletArea")] + public Area[] TabletAreas; + public Area SelectedTabletArea; + public Area TabletFullArea; public bool ForceAspectRatio; public double Rotation; - public bool ForceFullArea; - public OutputModes OutputMode; - public enum OutputModes + public bool Invert; + public OutputPositioning Positioning; + public OutputModes Mode; + + public enum OutputPositioning { Absolute = 0, - Relative = 1, - Digitizer = 2 + Relative = 1 + } + public enum OutputModes + { + Standard = 0, + WindowsInk = 1, + Compatibility = 2 } - public Area ScreenArea; - + // Smoothing filter + public bool SmoothingEnabled; public double SmoothingLatency; public int SmoothingInterval; - public bool SmoothingEnabled; + public bool SmoothingOnlyWhenButtons; + + // Noise filter + public bool NoiseFilterEnabled; + public int NoiseFilterBuffer; + public double NoiseFilterThreshold; + + // Anti-smoothing filter + public bool AntiSmoothingEnabled; + public class AntiSmoothingSetting + { + public bool Enabled; + public double Velocity; + public double Shape; + public double Compensation; + public AntiSmoothingSetting() + { + Enabled = false; + Velocity = 0; + Shape = 0.5; + Compensation = 0; + } + }; + [XmlArray("AntiSmoothingSettingsList")] + [XmlArrayItem("AntiSmoothingSettings")] + public AntiSmoothingSetting[] AntiSmoothingSettings; + public bool AntiSmoothingOnlyWhenHover; + public double AntiSmoothingDragMultiplier; public Area DesktopSize; public bool AutomaticDesktopSize; [XmlArray("ButtonMap")] [XmlArrayItem("Button")] - public int[] ButtonMap; + public string[] ButtonMap; public bool DisableButtons; - [XmlArray("CommandsAfter")] - [XmlArrayItem("Command")] - public string[] CommandsAfter; + [XmlArray("TabletButtonMap")] + [XmlArrayItem("Button")] + public string[] TabletButtonMap; + public bool DisableTabletButtons; + + public double PressureSensitivity; + public double PressureDeadzoneLow; + public double PressureDeadzoneHigh; - [XmlArray("CommandsBefore")] + public double ScrollSensitivity; + public double ScrollAcceleration; + public bool ScrollStopCursor; + public bool ScrollDrag; + + [XmlArray("CustomCommands")] [XmlArrayItem("Command")] - public string[] CommandsBefore; + public string[] CustomCommands; public int WindowWidth; public int WindowHeight; + public class TabletViewSettings + { + public string BackgroundColor; + public string InfoColor; + public string InputColor; + public string OutputColor; + public string LatencyColor; + public string DrawColor; + public int InputTrailLength; + public int OutputTrailLength; + public int DrawLength; + public string Font; + public double FontSize; + public Point OffsetText; + public Point OffsetPressure; + public bool FadeInOut; + public bool Borderless; + public TabletViewSettings() + { + BackgroundColor = "#FFFFFF"; + InfoColor = "#000000"; + InputColor = "#33AA33"; + OutputColor = "#AA3333"; + LatencyColor = "#3333AA"; + DrawColor = "#000000"; + InputTrailLength = 30; + OutputTrailLength = 30; + DrawLength = 0; + Font = "Segoe UI"; + FontSize = 25; + OffsetPressure = new Point(0, 0); + OffsetText = new Point(0, 0); + FadeInOut = false; + Borderless = false; + } + }; + public TabletViewSettings TabletView; + + public bool AutomaticRestart; public bool RunAtStartup; public string DriverPath; - public string DriverArguments; + public string DriverArguments; + public bool DebuggingEnabled; public bool DeveloperMode; + public class Preset + { + public string Name; + public Action Action; + public Preset(string name, Action action) + { + Name = name; + Action = action; + } + public override string ToString() + { + return Name; + } + } + public Configuration() { - ConfigVersion = 1; + ConfigVersion = 2; // Screen Map - ScreenArea = new Area(0, 0, 0, 0); + ScreenArea = null; + ScreenAreas = new Area[3]; + for (int i = 0; i < ScreenAreas.Length; i++) + { + ScreenAreas[i] = new Area(0, 0, 0, 0) + { + IsEnabled = false + }; + } + ScreenAreas[0].IsEnabled = true; + ScreenAreas[1] = new Area(1000, 500, 500, 250); // Tablet area - TabletArea = new Area(80, 45, 40, 22.5); + TabletArea = null; + TabletAreas = new Area[ScreenAreas.Length]; + for (int i = 0; i < GetAreaCount(); i++) + TabletAreas[i] = new Area(100, 56, 50, 28); TabletFullArea = new Area(100, 50, 50, 25); - ForceFullArea = true; - OutputMode = 0; + Mode = OutputModes.Standard; ForceAspectRatio = true; Rotation = 0; DesktopSize = new Area(0, 0, 0, 0); AutomaticDesktopSize = true; - ButtonMap = new int[] { 1, 2, 3 }; + ButtonMap = new string[] { "MOUSE1", "MOUSE2", "MOUSE3" }; DisableButtons = false; + TabletButtonMap = new string[16]; + for (int i = 0; i < 16; i++) TabletButtonMap[i] = ""; + DisableTabletButtons = false; + + PressureSensitivity = 0; + ScrollSensitivity = 0.5; + ScrollAcceleration = 1.0; + ScrollStopCursor = false; + SmoothingEnabled = false; SmoothingLatency = 0; SmoothingInterval = 4; + SmoothingOnlyWhenButtons = false; + + NoiseFilterEnabled = false; + NoiseFilterBuffer = 10; + NoiseFilterThreshold = 0.5; - CommandsAfter = new string[] { "" }; - CommandsBefore = new string[] { "" }; + AntiSmoothingEnabled = false; + AntiSmoothingSettings = new AntiSmoothingSetting[5]; + for (int i = 0; i < AntiSmoothingSettings.Length; i++) + AntiSmoothingSettings[i] = new AntiSmoothingSetting(); + AntiSmoothingDragMultiplier = 1.0; + AntiSmoothingOnlyWhenHover = false; - WindowWidth = 800; - WindowHeight = 710; + CustomCommands = new string[] { "" }; + WindowWidth = 700; + WindowHeight = 700; + + TabletView = new TabletViewSettings(); + + AutomaticRestart = true; RunAtStartup = false; DriverPath = "bin/TabletDriverService.exe"; DriverArguments = "config/init.cfg"; + + DebuggingEnabled = false; DeveloperMode = false; } + // + // Get number of areas + // + public int GetAreaCount() + { + if (ScreenAreas.Length <= TabletAreas.Length) + return ScreenAreas.Length; + return TabletAreas.Length; + } + + + // + // Get maximum number of areas + // + public int GetMaxAreaCount() + { + return 5; + } + + + // + // Get number of enabled areas + // + public int GetEnabledAreaCount() + { + int count = 0; + foreach (Area area in ScreenAreas) + { + if (area.IsEnabled) count++; + } + return count; + } + + + // + // Clear anti-smoothing filter settings + // + public void ClearAntiSmoothingSettings() + { + for (int i = 0; i < AntiSmoothingSettings.Length; i++) + { + AntiSmoothingSettings[i].Enabled = false; + AntiSmoothingSettings[i].Velocity = 0; + AntiSmoothingSettings[i].Shape = 0.5; + AntiSmoothingSettings[i].Compensation = 0; + } + } + + + // + // Set anti-smoothing filter settings + // + public void SetAntiSmoothingSetting(int index, bool enabled, double velocity, double shape, double compensation) + { + AntiSmoothingSettings[index].Enabled = enabled; + AntiSmoothingSettings[index].Velocity = velocity; + AntiSmoothingSettings[index].Shape = shape; + AntiSmoothingSettings[index].Compensation = compensation; + } + + + // + // Write configuration to a XML file + // public void Write(string filename) { var fileWriter = new StreamWriter(filename); @@ -115,6 +328,9 @@ public void Write(string filename) fileWriter.Close(); } + // + // Create configuration from a XML file + // public static Configuration CreateFromFile(string filename) { Configuration config = null; @@ -134,6 +350,7 @@ public static Configuration CreateFromFile(string filename) reader.Close(); return config; } + } diff --git a/TabletDriverGUI/MainWindow.Areas.cs b/TabletDriverGUI/MainWindow.Areas.cs new file mode 100644 index 0000000..a660d7d --- /dev/null +++ b/TabletDriverGUI/MainWindow.Areas.cs @@ -0,0 +1,1554 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Shapes; +using System.Windows.Threading; + +namespace TabletDriverGUI +{ + public partial class MainWindow : Window + { + + // + // Screen map canvas elements + // + private Rectangle[] rectangleMonitors; + private Rectangle rectangleDesktop; + private Rectangle[] rectangleScreenAreas; + private Image imageDesktopScreenshot; + private Area lastDesktopSize; + private Matrix matrixScreenAreaToCanvas; + private Matrix matrixCanvasToScreenArea; + + // + // Tablet area canvas elements + // + private Polygon polygonTabletFullArea; + private Polygon[] polygonTabletAreas; + private Polygon polygonTabletAreaArrow; + private Ellipse ellipsePenPosition; + private Ellipse ellipsePenPosition2; + private int lastPenPositionIndex; + private Matrix matrixTabletAreaToCanvas; + private Matrix matrixCanvasToTabletArea; + + // Area colors + private Brush[] brushBackgrounds; + private Brush brushStrokeSelected; + private Brush brushStrokeNormal; + private static double selectedStrokeThickness = 4.0; + + + // Canvas clicked area + private class MouseArea + { + public Area Area; + public Point Point; + public int Index; + public bool IsValid; + public MouseArea() + { + Area = null; + Point = new Point(0, 0); + Index = 0; + IsValid = false; + } + } + private MouseArea mouseArea; + + + // Canvas Mouse drag + private class MouseDrag + { + public bool IsMouseDown; + public object Source; + public Point OriginMouse; + public Point OriginDraggable; + public Area DragArea; + public MouseDrag() + { + IsMouseDown = false; + Source = null; + OriginMouse = new Point(0, 0); + OriginDraggable = new Point(0, 0); + } + } + MouseDrag mouseDrag; + + + + // + // Get desktop size + // + System.Drawing.Rectangle GetVirtualDesktopSize() + { + System.Drawing.Rectangle rect = new System.Drawing.Rectangle(); + + // Windows 8 or greater needed for the multiscreen absolute mode + if (VersionHelper.IsWindows8OrGreater()) + { + rect.Width = System.Windows.Forms.SystemInformation.VirtualScreen.Width; + rect.Height = System.Windows.Forms.SystemInformation.VirtualScreen.Height; + } + else if (config.Mode != Configuration.OutputModes.Compatibility) + { + rect.Width = System.Windows.Forms.SystemInformation.VirtualScreen.Width; + rect.Height = System.Windows.Forms.SystemInformation.VirtualScreen.Height; + } + else + { + rect.Width = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width; + rect.Height = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height; + + } + return rect; + } + + + // + // Get available screens + // + System.Windows.Forms.Screen[] GetAvailableScreens() + { + System.Windows.Forms.Screen[] screens; + + // Windows 8 or greater needed for the multiscreen absolute mode + if (VersionHelper.IsWindows8OrGreater()) + screens = System.Windows.Forms.Screen.AllScreens; + else if (config.Mode != Configuration.OutputModes.Compatibility) + screens = System.Windows.Forms.Screen.AllScreens; + else + screens = new System.Windows.Forms.Screen[] { System.Windows.Forms.Screen.PrimaryScreen }; + return screens; + } + + // + // Get minimum screen position + // + Vector GetMinimumScreenPosition(System.Windows.Forms.Screen[] screens) + { + + // Monitor minimums + double minX = 0; + double minY = 0; + bool first = true; + foreach (System.Windows.Forms.Screen screen in screens) + { + if (first) + { + minX = screen.Bounds.X; + minY = screen.Bounds.Y; + first = false; + } + else + { + if (screen.Bounds.X < minX) minX = screen.Bounds.X; + if (screen.Bounds.Y < minY) minY = screen.Bounds.Y; + } + } + + return new Vector(minX, minY); + } + + // + // Fix tablet area dimension + // + void FixTabletAreaDimensions(Area tabletArea, Area screenArea) + { + // Limits + if (tabletArea.Width > config.TabletFullArea.Width) + tabletArea.Width = config.TabletFullArea.Width; + if (tabletArea.Height > config.TabletFullArea.Height) + tabletArea.Height = config.TabletFullArea.Height; + + // Aspect ratio + if (config.ForceAspectRatio) + { + double aspectRatio = screenArea.Width / screenArea.Height; + tabletArea.Height = tabletArea.Width / aspectRatio; + if (tabletArea.Height > config.TabletFullArea.Height) + { + tabletArea.Height = config.TabletFullArea.Height; + tabletArea.Width = tabletArea.Height * aspectRatio; + } + } + } + + + // + // Create canvas elements + // + void CreateCanvasElements() + { + // + // Screen map canvas + // + // Clear canvas + canvasScreenMap.Children.Clear(); + + // + // Area background brushes + // + brushBackgrounds = new SolidColorBrush[10]; + Color[] colors = new Color[10]; + for (int i = 0; i < colors.Length; i++) + { + colors[i] = Color.FromArgb(20, 20, 20, 20); + } + + colors[0] = Color.FromRgb(186, 255, 201); + colors[1] = Color.FromRgb(186, 225, 255); + colors[2] = Color.FromRgb(255, 223, 186); + colors[3] = Color.FromRgb(255, 179, 186); + colors[4] = Color.FromRgb(255, 255, 150); + + for (int i = 0; i < brushBackgrounds.Length; i++) + { + double multiplier = 0.9; + colors[i].R = (byte)(Math.Round(colors[i].R * multiplier)); + colors[i].G = (byte)(Math.Round(colors[i].G * multiplier)); + colors[i].B = (byte)(Math.Round(colors[i].B * multiplier)); + colors[i].A = 128; + brushBackgrounds[i] = new SolidColorBrush(colors[i]); + } + + // + // Selected brushes + // + brushStrokeSelected = new SolidColorBrush(Color.FromArgb(128, 255, 30, 30)); + brushStrokeNormal = new SolidColorBrush(Color.FromArgb(255, 0, 0, 0)); + + + // + // Screen map desktop screenshot + // + imageDesktopScreenshot = new Image + { + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + Width = 100, + Height = 100, + Opacity = 0.7 + }; + canvasScreenMap.Children.Add(imageDesktopScreenshot); + lastDesktopSize = new Area(0, 0, 0, 0); + + + // Monitor rectangles + rectangleMonitors = new Rectangle[16]; + for (int i = 0; i < 16; i++) + { + rectangleMonitors[i] = new Rectangle + { + Width = 10, + Height = 10, + Stroke = Brushes.Black, + StrokeThickness = 0.5, + Fill = Brushes.Transparent, + Visibility = Visibility.Collapsed + }; + canvasScreenMap.Children.Add(rectangleMonitors[i]); + } + + // + // Desktop area rectangle + // + rectangleDesktop = new Rectangle + { + Stroke = Brushes.Black, + StrokeThickness = 0.5, + Fill = Brushes.Transparent + }; + canvasScreenMap.Children.Add(rectangleDesktop); + + + // + // Screen map area rectangles + // + rectangleScreenAreas = new Rectangle[config.GetMaxAreaCount()]; + for (int i = config.GetMaxAreaCount() - 1; i >= 0; i--) + { + rectangleScreenAreas[i] = new Rectangle + { + Stroke = Brushes.Black, + StrokeThickness = 1.5, + Fill = brushBackgrounds[i], + Cursor = Cursors.Hand, + Visibility = Visibility.Collapsed + }; + canvasScreenMap.Children.Add(rectangleScreenAreas[i]); + } + + // Screen area matrix + matrixScreenAreaToCanvas = new Matrix(0, 0, 0, 0, 0, 0); + matrixCanvasToScreenArea = new Matrix(0, 0, 0, 0, 0, 0); + + // + // Tablet area canvas + // + // + // Clear + canvasTabletArea.Children.Clear(); + + // + // Tablet full area polygon + // + polygonTabletFullArea = new Polygon + { + Stroke = new SolidColorBrush(Color.FromRgb(100, 100, 100)), + StrokeThickness = 2.0, + Points = new PointCollection + { + new Point(0,0), + new Point(0,0), + new Point(0,0), + new Point(0,0) + } + }; + canvasTabletArea.Children.Add(polygonTabletFullArea); + + // + // Tablet area polygons + // + polygonTabletAreas = new Polygon[config.GetMaxAreaCount()]; + for (int i = config.GetMaxAreaCount() - 1; i >= 0; i--) + { + polygonTabletAreas[i] = new Polygon + { + Stroke = Brushes.Black, + StrokeLineJoin = PenLineJoin.Round, + Fill = brushBackgrounds[i], + StrokeThickness = 1.5, + Cursor = Cursors.Hand, + Points = new PointCollection + { + new Point(0,0), + new Point(0,0), + new Point(0,0), + new Point(0,0) + }, + }; + canvasTabletArea.Children.Add(polygonTabletAreas[i]); + } + + + // + // Tablet area arrow polygon + // + polygonTabletAreaArrow = new Polygon + { + Fill = new SolidColorBrush(Color.FromArgb(50, 20, 20, 20)), + Cursor = Cursors.Hand, + Points = new PointCollection + { + new Point(0,0), + new Point(0,0), + new Point(0,0) + }, + }; + canvasTabletArea.Children.Add(polygonTabletAreaArrow); + + // + // Tablet area pen position + // + ellipsePenPosition = new Ellipse + { + Stroke = Brushes.Green, + StrokeThickness = 1, + Width = 5, + Height = 5, + Visibility = Visibility.Collapsed + }; + canvasTabletArea.Children.Add(ellipsePenPosition); + + ellipsePenPosition2 = new Ellipse + { + Stroke = Brushes.Red, + StrokeThickness = 1, + Width = 5, + Height = 5, + Visibility = Visibility.Collapsed + }; + canvasTabletArea.Children.Add(ellipsePenPosition2); + + + timerUpdatePenPositions = new DispatcherTimer + { + Interval = new TimeSpan(0, 0, 0, 0, 20) + }; + lastPenPositionIndex = 0; + timerUpdatePenPositions.Tick += (sender, e) => + { + if (lastPenPositionIndex == 0) + { + ellipsePenPosition.Visibility = Visibility.Visible; + ellipsePenPosition2.Visibility = Visibility.Visible; + } + if (driver.tabletState.index != lastPenPositionIndex) + { + UpdateTabletAreaCanvasPenPositions(); + lastPenPositionIndex = driver.tabletState.index; + } + }; + timerUpdatePenPositions.Stop(); + + + // + // Tablet area matrix + // + matrixTabletAreaToCanvas = new Matrix(0, 0, 0, 0, 0, 0); + matrixCanvasToTabletArea = new Matrix(0, 0, 0, 0, 0, 0); + + // Canvas mouse area + mouseArea = new MouseArea(); + + // + // Canvas mouse drag + // + mouseDrag = new MouseDrag(); + } + + + // + // Update canvas elements + // + void UpdateCanvasElements() + { + if (config == null) return; + if (isLoadingSettings) return; + UpdateDesktopImage(); + UpdateScreenMapCanvas(); + UpdateTabletAreaCanvas(); + UpdateAreaInformation(); + } + + + // + // Update screen map desktop image + // + void UpdateDesktopImage() + { + if (imageDesktopScreenshot != null && imageDesktopScreenshot.Source != null) + { + if ( + config.DesktopSize.Width == lastDesktopSize.Width + && + config.DesktopSize.Height == lastDesktopSize.Height + ) + { + return; + } + } + + try + { + int screenLeft = System.Windows.Forms.SystemInformation.VirtualScreen.Left; + int screenTop = System.Windows.Forms.SystemInformation.VirtualScreen.Top; + int screenWidth = System.Windows.Forms.SystemInformation.VirtualScreen.Width; + int screenHeight = System.Windows.Forms.SystemInformation.VirtualScreen.Height; + + + // Create desktop screenshot bitmap + System.Drawing.Bitmap bitmapDesktop = new System.Drawing.Bitmap(screenWidth, screenHeight); + System.Drawing.Graphics graphicsDesktop = System.Drawing.Graphics.FromImage(bitmapDesktop); + graphicsDesktop.CopyFromScreen(screenLeft, screenTop, 0, 0, bitmapDesktop.Size, System.Drawing.CopyPixelOperation.SourceCopy); + + + // Create downscaled bitmap + double scaleX = canvasScreenMap.ActualWidth / screenWidth; + double scaleY = canvasScreenMap.ActualHeight / screenHeight; + double scale = scaleX; + if (scaleX > scaleY) + scale = scaleY; + System.Drawing.Bitmap bitmapDownscaled = new System.Drawing.Bitmap( + (int)Math.Round(screenWidth * scale), + (int)Math.Round(screenHeight * scale) + ); + System.Drawing.Graphics graphicsDownscaled = System.Drawing.Graphics.FromImage(bitmapDownscaled); + graphicsDownscaled.DrawImage(bitmapDesktop, 0, 0, bitmapDownscaled.Width, bitmapDownscaled.Height); + + + // Create source from the bitmap + IntPtr handleBitmap = bitmapDownscaled.GetHbitmap(); + BitmapSource bitmapSource = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap( + handleBitmap, + IntPtr.Zero, + Int32Rect.Empty, + BitmapSizeOptions.FromEmptyOptions() + ); + imageDesktopScreenshot.Source = bitmapSource; + + // Release resources + NativeMethods.DeleteObject(handleBitmap); + graphicsDesktop.Dispose(); + bitmapDesktop.Dispose(); + graphicsDownscaled.Dispose(); + bitmapDownscaled.Dispose(); + + // Run GC + GC.Collect(); + GC.WaitForPendingFinalizers(); + + // Update last desktop size + lastDesktopSize.Width = config.DesktopSize.Width; + lastDesktopSize.Height = config.DesktopSize.Height; + + } + catch (Exception) + { + } + } + + + // + // Update screen map canvas elements + // + void UpdateScreenMapCanvas() + { + + // Canvas element scaling + double scaleX = (canvasScreenMap.ActualWidth - 2) / config.DesktopSize.Width; + double scaleY = (canvasScreenMap.ActualHeight - 2) / config.DesktopSize.Height; + double scale = scaleX; + if (scaleX > scaleY) + scale = scaleY; + + // Centered offset + double offsetX = canvasScreenMap.ActualWidth / 2.0 - config.DesktopSize.Width * scale / 2.0; + double offsetY = canvasScreenMap.ActualHeight / 2.0 - config.DesktopSize.Height * scale / 2.0; + + // Matrices + matrixScreenAreaToCanvas.M11 = scale; + matrixScreenAreaToCanvas.M22 = scale; + matrixScreenAreaToCanvas.OffsetX = offsetX; + matrixScreenAreaToCanvas.OffsetY = offsetY; + + matrixCanvasToScreenArea.M11 = 1 / scale; + matrixCanvasToScreenArea.M22 = 1 / scale; + matrixCanvasToScreenArea.OffsetX = -offsetX / scale; + matrixCanvasToScreenArea.OffsetY = -offsetY / scale; + + + // Full desktop area + rectangleDesktop.Width = config.DesktopSize.Width * scale; + rectangleDesktop.Height = config.DesktopSize.Height * scale; + Canvas.SetLeft(rectangleDesktop, offsetX); + Canvas.SetTop(rectangleDesktop, offsetY); + + imageDesktopScreenshot.Width = config.DesktopSize.Width * scale; + imageDesktopScreenshot.Height = config.DesktopSize.Height * scale; + Canvas.SetLeft(imageDesktopScreenshot, offsetX); + Canvas.SetTop(imageDesktopScreenshot, offsetY); + + + // + // Screen map area rectangles + // + for (int i = 0; i < config.GetAreaCount(); i++) + { + rectangleScreenAreas[i].Width = config.ScreenAreas[i].Width * scale; + rectangleScreenAreas[i].Height = config.ScreenAreas[i].Height * scale; + Canvas.SetLeft(rectangleScreenAreas[i], offsetX + (config.ScreenAreas[i].X - config.ScreenAreas[i].Width / 2.0) * scale); + Canvas.SetTop(rectangleScreenAreas[i], offsetY + (config.ScreenAreas[i].Y - config.ScreenAreas[i].Height / 2.0) * scale); + + if (mouseArea.IsValid && mouseArea.Index == i) + { + rectangleScreenAreas[i].Stroke = brushStrokeSelected; + rectangleScreenAreas[i].StrokeThickness = selectedStrokeThickness; + } + else + { + rectangleScreenAreas[i].Stroke = brushStrokeNormal; + rectangleScreenAreas[i].StrokeThickness = 1.5; + } + + if (config.ScreenAreas[i].IsEnabled) + { + rectangleScreenAreas[i].Visibility = Visibility.Visible; + } + else + { + rectangleScreenAreas[i].Visibility = Visibility.Collapsed; + } + } + + // Only primary area enabled -> remove red border + if (config.GetEnabledAreaCount() == 1) + { + rectangleScreenAreas[0].Stroke = brushStrokeNormal; + rectangleScreenAreas[0].StrokeThickness = 1.5; + + } + + // + // Monitor rectangles + // + System.Windows.Forms.Screen[] screens = GetAvailableScreens(); + Vector minimumScreenPosition = GetMinimumScreenPosition(screens); + + // Hide all rectangles + for (int i = 0; i < rectangleMonitors.Length; i++) + rectangleMonitors[i].Visibility = Visibility.Collapsed; + + int rectangeIndex = 0; + foreach (System.Windows.Forms.Screen screen in screens) + { + double x = screen.Bounds.X - minimumScreenPosition.X; + double y = screen.Bounds.Y - minimumScreenPosition.Y; + + rectangleMonitors[rectangeIndex].Visibility = Visibility.Visible; + rectangleMonitors[rectangeIndex].Width = screen.Bounds.Width * scale; + rectangleMonitors[rectangeIndex].Height = screen.Bounds.Height * scale; + Canvas.SetLeft(rectangleMonitors[rectangeIndex], offsetX + x * scale); + Canvas.SetTop(rectangleMonitors[rectangeIndex], offsetY + y * scale); + + rectangeIndex++; + if (rectangeIndex >= 16) break; + } + + + canvasScreenMap.InvalidateVisual(); + + } + + + // + // Update tablet area canvas elements + // + void UpdateTabletAreaCanvas() + { + double fullWidth = config.TabletFullArea.Width; + double fullHeight = config.TabletFullArea.Height; + + // Canvas element scaling + double scaleX = (canvasTabletArea.ActualWidth - 2) / fullWidth; + double scaleY = (canvasTabletArea.ActualHeight - 2) / fullHeight; + double scale = scaleX; + if (scaleX > scaleY) + scale = scaleY; + + double offsetX = canvasTabletArea.ActualWidth / 2.0 - fullWidth * scale / 2.0; + double offsetY = canvasTabletArea.ActualHeight / 2.0 - fullHeight * scale / 2.0; + + // Matrices + matrixTabletAreaToCanvas.M11 = scale; + matrixTabletAreaToCanvas.M22 = scale; + matrixTabletAreaToCanvas.OffsetX = offsetX; + matrixTabletAreaToCanvas.OffsetY = offsetY; + + matrixCanvasToTabletArea.M11 = 1 / scale; + matrixCanvasToTabletArea.M22 = 1 / scale; + matrixCanvasToTabletArea.OffsetX = -offsetX / scale; + matrixCanvasToTabletArea.OffsetY = -offsetY / scale; + + + + // + // Tablet full area + // + Point[] corners = config.TabletFullArea.Corners; + for (int i = 0; i < 4; i++) + { + Point p = corners[i]; + p.X *= scale; + p.Y *= scale; + p.X += config.TabletFullArea.X * scale + offsetX; + p.Y += config.TabletFullArea.Y * scale + offsetY; + polygonTabletFullArea.Points[i] = p; + } + + + // + // Tablet areas + // + for (int i = 0; i < config.GetAreaCount(); i++) + { + corners = config.TabletAreas[i].Corners; + for (int j = 0; j < 4; j++) + { + Point p = corners[j]; + p.X *= scale; + p.Y *= scale; + p.X += config.TabletAreas[i].X * scale + offsetX; + p.Y += config.TabletAreas[i].Y * scale + offsetY; + polygonTabletAreas[i].Points[j] = p; + } + + + if (mouseArea.IsValid && mouseArea.Index == i) + { + polygonTabletAreas[i].Stroke = brushStrokeSelected; + polygonTabletAreas[i].StrokeThickness = selectedStrokeThickness; + } + else + { + polygonTabletAreas[i].Stroke = brushStrokeNormal; + polygonTabletAreas[i].StrokeThickness = 1.5; + } + + if (config.ScreenAreas[i].IsEnabled) + { + polygonTabletAreas[i].Visibility = Visibility.Visible; + } + else + { + polygonTabletAreas[i].Visibility = Visibility.Collapsed; + } + } + + // Only primary area enabled -> remove red border + if (config.GetEnabledAreaCount() == 1) + { + polygonTabletAreas[0].Stroke = brushStrokeNormal; + polygonTabletAreas[0].StrokeThickness = 1.5; + } + + + + // + // Tablet area arrow + // + corners = config.TabletAreas[0].Corners; + polygonTabletAreaArrow.Points[0] = new Point( + offsetX + config.TabletAreas[0].X * scale, + offsetY + config.TabletAreas[0].Y * scale + ); + + polygonTabletAreaArrow.Points[1] = new Point( + offsetX + corners[2].X * scale + config.TabletAreas[0].X * scale, + offsetY + corners[2].Y * scale + config.TabletAreas[0].Y * scale + ); + + polygonTabletAreaArrow.Points[2] = new Point( + offsetX + corners[3].X * scale + config.TabletAreas[0].X * scale, + offsetY + corners[3].Y * scale + config.TabletAreas[0].Y * scale + ); + + + canvasTabletArea.InvalidateVisual(); + + } + + + + // + // Update canvas pen positions + // + void UpdateTabletAreaCanvasPenPositions() + { + Point p = new Point(); + + // Inverted + if (config.Invert) + { + // Input position + p.X = config.TabletFullArea.Width - driver.tabletState.inputX; + p.Y = config.TabletFullArea.Height - driver.tabletState.inputY; + matrixTabletAreaToCanvas.Transform(p); + Canvas.SetLeft(ellipsePenPosition, p.X); + Canvas.SetTop(ellipsePenPosition, p.Y); + + // Output position + p.X = config.TabletFullArea.Width - driver.tabletState.outputX; + p.Y = config.TabletFullArea.Height - driver.tabletState.outputY; + matrixTabletAreaToCanvas.Transform(p); + Canvas.SetLeft(ellipsePenPosition2, p.X); + Canvas.SetTop(ellipsePenPosition2, p.Y); + } + else + { + // Input position + p.X = driver.tabletState.inputX; + p.Y = driver.tabletState.inputY; + matrixTabletAreaToCanvas.Transform(p); + Canvas.SetLeft(ellipsePenPosition, p.X); + Canvas.SetTop(ellipsePenPosition, p.Y); + + // Output position + p.X = driver.tabletState.outputX; + p.Y = driver.tabletState.outputY; + matrixTabletAreaToCanvas.Transform(p); + Canvas.SetLeft(ellipsePenPosition2, p.X); + Canvas.SetTop(ellipsePenPosition2, p.Y); + } + + } + + // + // Get area coordinates from canvas position + // + Point GetAreaCoordinates(UIElement canvas, Point p) + { + Point point = new Point(p.X, p.Y); + + if (canvas == canvasScreenMap) + { + point = matrixCanvasToScreenArea.Transform(point); + } + else if (canvas == canvasTabletArea) + { + point = matrixCanvasToTabletArea.Transform(point); + } + + return point; + } + + + // + // Update last clicked area + // + void UpdateMouseArea(UIElement sender, Point cursorPosition) + { + Area area = null; + Point areaPoint = new Point(0, 0); + int index = -1; + + Point clickPosition = GetAreaCoordinates(sender, cursorPosition); + + // + // Screen areas + // + if (sender == canvasScreenMap) + { + for (int i = 0; i < config.GetAreaCount(); i++) + { + if ( + config.ScreenAreas[i].IsEnabled + && + config.ScreenAreas[i].IsInside(clickPosition) + && + + // Area smaller than last found area? + ( + area == null + || + area.Width > config.ScreenAreas[i].Width + ) + ) + { + area = config.ScreenAreas[i]; + config.SelectedScreenArea = config.ScreenAreas[i]; + config.SelectedTabletArea = config.TabletAreas[i]; + index = i; + } + } + if (index == -1) + { + config.SelectedScreenArea = config.ScreenAreas[0]; + config.SelectedTabletArea = config.TabletAreas[0]; + } + } + + + // + // Tablet areas + // + else if (sender == canvasTabletArea) + { + for (int i = 0; i < config.GetAreaCount(); i++) + { + if ( + config.ScreenAreas[i].IsEnabled + && + config.TabletAreas[i].IsInside(clickPosition) + && + + // Area smaller than last found area? + ( + area == null + || + area.Width > config.TabletAreas[i].Width + ) + ) + { + area = config.TabletAreas[i]; + config.SelectedScreenArea = config.ScreenAreas[i]; + config.SelectedTabletArea = config.TabletAreas[i]; + index = i; + } + } + if (index == -1) + { + config.SelectedScreenArea = config.ScreenAreas[0]; + config.SelectedTabletArea = config.TabletAreas[0]; + } + } + + if (area != null) + { + mouseArea.Area = area; + mouseArea.Index = index; + mouseArea.IsValid = true; + } + else + { + mouseArea.Area = null; + mouseArea.Index = index; + mouseArea.IsValid = false; + } + mouseArea.Point.X = clickPosition.X; + mouseArea.Point.Y = clickPosition.Y; + + + } + + // + // Update area information + // + void UpdateAreaInformation() + { + // Update area information label + int areaIndex = mouseArea.Index; + if (areaIndex < 0) areaIndex = 0; + string areaText = "AREA #" + (areaIndex + 1) + " | "; + if (config.GetEnabledAreaCount() == 1) areaText = ""; + + // Screen area + labelScreenAreaInfo.Content = areaText + + Utils.GetNumberString(config.SelectedScreenArea.Width / config.SelectedScreenArea.Height, "0.000") + ":1 | " + + Utils.GetNumberString(config.SelectedScreenArea.Width * config.SelectedScreenArea.Height, "0") + " pixels"; + + // Tablet area + labelTabletAreaInfo.Content = areaText + + Utils.GetNumberString(config.SelectedTabletArea.Width / config.SelectedTabletArea.Height, "0.000") + ":1 | " + + Utils.GetNumberString(config.SelectedTabletArea.Width * config.SelectedTabletArea.Height, "0") + " mm² " + + Utils.GetNumberString( + config.SelectedTabletArea.Width * config.SelectedTabletArea.Height / + (config.TabletFullArea.Width * config.TabletFullArea.Height) * 100.0 + , "0") + "% of " + + Utils.GetNumberString(config.TabletFullArea.Width) + "x" + Utils.GetNumberString(config.TabletFullArea.Height) + " mm | " + + Utils.GetNumberString(config.SelectedScreenArea.Width / config.SelectedTabletArea.Width, "0.0") + "x" + + Utils.GetNumberString(config.SelectedScreenArea.Height / config.SelectedTabletArea.Height, "0.0") + " px/mm"; + + } + + // + // Canvas mouse events + // + // + // Canvas mouse down + private void CanvasArea_MouseDown(object sender, MouseButtonEventArgs e) + { + bool mouseDown = false; + + // Is the button left mouse button? + if (e.LeftButton == MouseButtonState.Pressed) + mouseDown = true; + + // Update last clicked area + Point cursorPosition = e.GetPosition((UIElement)sender); + UpdateMouseArea((UIElement)sender, cursorPosition); + + if (sender != canvasScreenMap && sender != canvasTabletArea) return; + + mouseDrag.Source = (UIElement)sender; + mouseDrag.OriginMouse = cursorPosition; + + // + // Screen map drag + // + if (mouseDrag.Source == canvasScreenMap) + { + if (!mouseArea.IsValid) return; + mouseDrag.DragArea = mouseArea.Area; + + // Reset monitor selection + comboBoxMonitor.SelectedIndex = -1; + + mouseDrag.IsMouseDown = mouseDown; + mouseDrag.OriginDraggable = new Point(mouseDrag.DragArea.X, mouseDrag.DragArea.Y); + canvasScreenMap.CaptureMouse(); + } + + // + // Tablet area drag + // + else if (mouseDrag.Source == canvasTabletArea) + { + if (!mouseArea.IsValid) return; + mouseDrag.DragArea = mouseArea.Area; + + mouseDrag.IsMouseDown = mouseDown; + mouseDrag.OriginDraggable = new Point(mouseDrag.DragArea.X, mouseDrag.DragArea.Y); + canvasTabletArea.CaptureMouse(); + } + + } + + + // + // Canvas mouse up + // + private void CanvasArea_MouseUp(object sender, MouseButtonEventArgs e) + { + mouseDrag.IsMouseDown = false; + LoadSettingsFromConfiguration(); + //isLoadingSettings = true; + textScreenAreaX.Text = Utils.GetNumberString(config.SelectedScreenArea.X - config.SelectedScreenArea.Width / 2.0, "0"); + textScreenAreaY.Text = Utils.GetNumberString(config.SelectedScreenArea.Y - config.SelectedScreenArea.Height / 2.0, "0"); + textTabletAreaX.Text = Utils.GetNumberString(config.SelectedTabletArea.X); + textTabletAreaY.Text = Utils.GetNumberString(config.SelectedTabletArea.Y); + //isLoadingSettings = false; + canvasScreenMap.ReleaseMouseCapture(); + canvasTabletArea.ReleaseMouseCapture(); + + // Focus + if (sender == canvasScreenMap) + canvasScreenMap.Focus(); + else if (sender == canvasTabletArea) + canvasTabletArea.Focus(); + + + } + + + // + // Canvas mouse move + // + private void CanvasArea_MouseMove(object sender, MouseEventArgs e) + { + Point position; + double dx, dy; + double scaleX = 0, scaleY = 0, scale = 0; + double gridSize = 1; + + // Canvas mouse drag + if (mouseDrag.IsMouseDown && mouseDrag.Source == sender) + { + position = e.GetPosition((UIElement)mouseDrag.Source); + + dx = position.X - mouseDrag.OriginMouse.X; + dy = position.Y - mouseDrag.OriginMouse.Y; + + // Shift + Drag -> Only move up/down + if (Keyboard.Modifiers.HasFlag(ModifierKeys.Shift)) + dx = 0; + + // Control + Drag -> Only move left/right + if (Keyboard.Modifiers.HasFlag(ModifierKeys.Control)) + dy = 0; + + // Alt + Drag -> Use larger grid size + if (sender == canvasScreenMap && Keyboard.Modifiers.HasFlag(ModifierKeys.Alt)) + gridSize = 80; + if (sender == canvasTabletArea && Keyboard.Modifiers.HasFlag(ModifierKeys.Alt)) + gridSize = 5; + + + // Screen map canvas + if (mouseDrag.Source == canvasScreenMap && mouseDrag.DragArea != null) + { + scaleX = config.DesktopSize.Width / canvasScreenMap.ActualWidth; + scaleY = config.DesktopSize.Height / canvasScreenMap.ActualHeight; + scale = scaleY; + if (scaleX > scaleY) + scale = scaleX; + + // Grid + dx = Math.Round(dx * scale / gridSize) * gridSize / scale; + dy = Math.Round(dy * scale / gridSize) * gridSize / scale; + + // Move area + mouseDrag.DragArea.X = mouseDrag.OriginDraggable.X + dx * scale; + mouseDrag.DragArea.Y = mouseDrag.OriginDraggable.Y + dy * scale; + + LoadSettingsFromConfiguration(); + } + + // Tablet area canvas + else if (mouseDrag.Source == canvasTabletArea && mouseDrag.DragArea != null) + { + scaleX = config.TabletFullArea.Width / canvasTabletArea.ActualWidth; + scaleY = config.TabletFullArea.Height / canvasTabletArea.ActualHeight; + scale = scaleY; + if (scaleX > scaleY) + scale = scaleX; + + // Grid + dx = Math.Round(dx * scale / gridSize) * gridSize / scale; + dy = Math.Round(dy * scale / gridSize) * gridSize / scale; + + // Move area + mouseDrag.DragArea.X = mouseDrag.OriginDraggable.X + dx * scale; + mouseDrag.DragArea.Y = mouseDrag.OriginDraggable.Y + dy * scale; + + LoadSettingsFromConfiguration(); + } + } + + } + + + // + // Screen or tablet area mouse scroll + // + private void CanvasArea_MouseWheel(object sender, MouseWheelEventArgs e) + { + + // + // Screen or tablet area scroll + // + if (sender == canvasScreenMap || sender == canvasTabletArea) + { + if (!mouseArea.IsValid) return; + double delta = e.Delta / 120.0; + if (sender == canvasScreenMap) + { + delta *= 10; + } + if (Keyboard.Modifiers == ModifierKeys.Control) + { + delta *= 10; + } + + double oldWidth = mouseArea.Area.Width; + double oldHeight = mouseArea.Area.Height; + double newWidth = Math.Round(oldWidth + delta); + double newHeight = oldHeight / oldWidth * newWidth; + if (delta > 0 || newWidth >= 10 || newHeight >= 10) + { + mouseArea.Area.Width = newWidth; + mouseArea.Area.Height = newHeight; + } + LoadSettingsFromConfiguration(); + } + + } + + + // + // Canvas context menu click + // + private void CanvasArea_MenuClick(object sender, RoutedEventArgs e) + { + if (sender == null) return; + if (!(sender is MenuItem)) return; + MenuItem menuItem = (MenuItem)sender; + + + // + // Move screen area center of a monitor or set to full monitor + // + if (sender == menuCanvasMoveToMonitorCenter || sender == menuCanvasSetToFullMonitor) + { + if (!mouseArea.IsValid) return; + + // Get monitors + System.Windows.Forms.Screen[] screens = GetAvailableScreens(); + Vector minimumScreenPosition = GetMinimumScreenPosition(screens); + + // Loop through monitors + foreach (System.Windows.Forms.Screen screen in screens) + { + double x = screen.Bounds.X - minimumScreenPosition.X; + double y = screen.Bounds.Y - minimumScreenPosition.Y; + if ( + mouseArea.Point.X >= x && mouseArea.Point.X <= x + screen.Bounds.Width + && + mouseArea.Point.Y >= y && mouseArea.Point.Y <= y + screen.Bounds.Height + ) + { + mouseArea.Area.X = x + screen.Bounds.Width / 2.0; + mouseArea.Area.Y = y + screen.Bounds.Height / 2.0; + if (sender == menuCanvasSetToFullMonitor) + { + mouseArea.Area.Width = screen.Bounds.Width; + mouseArea.Area.Height = screen.Bounds.Height; + } + LoadSettingsFromConfiguration(); + break; + } + } + + } + + + // + // Set to full screen area + // + else if (sender == menuCanvasSetToFullDesktop) + { + if (!mouseArea.IsValid) return; + mouseArea.Area.X = config.DesktopSize.X; + mouseArea.Area.Y = config.DesktopSize.Y; + mouseArea.Area.Width = config.DesktopSize.Width; + mouseArea.Area.Height = config.DesktopSize.Height; + + LoadSettingsFromConfiguration(); + } + + + // + // Move tablet area to center + // + else if (sender == menuCanvasMoveToCenter) + { + if (!mouseArea.IsValid) return; + + mouseArea.Area.X = config.TabletFullArea.Width / 2.0; + mouseArea.Area.Y = config.TabletFullArea.Height / 2.0; + + LoadSettingsFromConfiguration(); + } + + + // + // Set to full tablet area + // + else if (sender == menuCanvasSetToFullTabletArea) + { + if (!mouseArea.IsValid) return; + mouseArea.Area.Width = config.TabletFullArea.Width; + mouseArea.Area.Height = config.TabletFullArea.Height; + mouseArea.Area.X = config.TabletFullArea.X; + mouseArea.Area.Y = config.TabletFullArea.Y; + FixTabletAreaDimensions(config.SelectedTabletArea, config.SelectedScreenArea); + LoadSettingsFromConfiguration(); + } + + // + // Force tablet area proportions + // + else if (sender == menuCanvasForceProportions) + { + double screenAspectRatio = config.SelectedScreenArea.Width / config.SelectedScreenArea.Height; + double tabletAspectRatio = config.SelectedTabletArea.Width / config.SelectedTabletArea.Height; + + // Tablet aspect ratio higher -> change width + if (tabletAspectRatio >= screenAspectRatio) + config.SelectedTabletArea.Width = config.SelectedTabletArea.Height * screenAspectRatio; + + // Tablet aspect ratio lower -> change height + else + config.SelectedTabletArea.Height = config.SelectedTabletArea.Width / screenAspectRatio; + + LoadSettingsFromConfiguration(); + } + + // + // Add secondary area + // + else if (sender == menuCanvasAddScreenArea || sender == menuCanvasAddTabletArea) + { + for (int i = 1; i < config.GetAreaCount(); i++) + { + // Area is not enabled? + if (!config.ScreenAreas[i].IsEnabled) + { + // Enable area + config.ScreenAreas[i].IsEnabled = true; + + // Screen area + if (sender == menuCanvasAddScreenArea) + { + config.ScreenAreas[i].X = mouseArea.Point.X; + config.ScreenAreas[i].Y = mouseArea.Point.Y; + } + else + { + config.ScreenAreas[i].X = config.DesktopSize.Width / 2.0; + config.ScreenAreas[i].Y = config.DesktopSize.Height / 2.0; + } + config.ScreenAreas[i].Width = 640; + config.ScreenAreas[i].Height = 640 * config.ScreenAreas[0].Height / config.ScreenAreas[0].Width; + + // Tablet area + if (sender == menuCanvasAddTabletArea) + { + config.TabletAreas[i].X = mouseArea.Point.X; + config.TabletAreas[i].Y = mouseArea.Point.Y; + } + else + { + config.TabletAreas[i].X = config.TabletFullArea.Width / 2.0; + config.TabletAreas[i].Y = config.TabletFullArea.Height / 2.0; + } + config.TabletAreas[i].Width = 50; + config.TabletAreas[i].Height = 50 * config.ScreenAreas[0].Height / config.ScreenAreas[0].Width; + + break; + } + } + LoadSettingsFromConfiguration(); + } + + // + // Remove area + // + else if (sender == menuCanvasRemoveScreenArea || sender == menuCanvasRemoveTabletArea) + { + if (!mouseArea.IsValid) return; + + WindowMessageBox messageBox = new WindowMessageBox( + "Are you sure?", "Remove area #" + (mouseArea.Index + 1) + "?", + "Yes", "No"); + messageBox.ShowDialog(); + if (messageBox.DialogResult == true) + { + bool removed = false; + for (int i = 1; i < config.GetAreaCount(); i++) + { + // Item to be removed found + if (!removed && config.ScreenAreas[i] == mouseArea.Area || config.TabletAreas[i] == mouseArea.Area) + { + removed = true; + } + + // Shift area arrays + else if (removed) + { + config.ScreenAreas[i - 1].Set(config.ScreenAreas[i]); + config.TabletAreas[i - 1].Set(config.TabletAreas[i]); + config.ScreenAreas[i].IsEnabled = false; + config.TabletAreas[i].IsEnabled = false; + } + } + + // Select primary area + if (removed) + { + config.SelectedScreenArea = config.ScreenAreas[0]; + config.SelectedTabletArea = config.TabletAreas[0]; + mouseArea.Area = null; + mouseArea.IsValid = false; + LoadSettingsFromConfiguration(); + } + } + } + + + // + // Edit area + // + else if (sender == menuCanvasEditScreenArea || sender == menuCanvasEditTabletArea) + { + if (!mouseArea.IsValid) return; + + int areaIndex = -1; + for (int i = 0; i < config.GetAreaCount(); i++) + { + if (config.ScreenAreas[i] == mouseArea.Area || config.TabletAreas[i] == mouseArea.Area) + { + areaIndex = i; + } + } + if (areaIndex >= 0) + { + WindowAreaEditor areaEditor = new WindowAreaEditor(config, config.ScreenAreas[areaIndex], config.TabletAreas[areaIndex]) + { + Title = "Area #" + (areaIndex + 1) + }; + areaEditor.ShowDialog(); + if (areaEditor.DialogResult == true) + { + LoadSettingsFromConfiguration(); + } + } + } + + + // + // Reset screen areas + // + else if (sender == menuCanvasResetScreenAreas) + { + WindowMessageBox messageBox = new WindowMessageBox( + "Are you sure?", "Reset all screen areas?", + "Yes", "No"); + messageBox.ShowDialog(); + if (messageBox.DialogResult == true) + { + double offsetY = -(config.GetEnabledAreaCount() * 100.0 / 2.0); + foreach (Area screenArea in config.ScreenAreas) + { + screenArea.X = config.DesktopSize.X; + screenArea.Y = config.DesktopSize.Y + offsetY; + screenArea.Width = config.DesktopSize.Width / 2.0; + screenArea.Height = config.DesktopSize.Height / 2.0; + offsetY += 100; + } + LoadSettingsFromConfiguration(); + } + } + + + // + // Reset tablet areas + // + else if (sender == menuCanvasResetTabletAreas) + { + WindowMessageBox messageBox = new WindowMessageBox( + "Are you sure?", "Reset all tablet areas?", + "Yes", "No"); + messageBox.ShowDialog(); + if (messageBox.DialogResult == true) + { + double offsetY = -(config.GetEnabledAreaCount() * 10.0 / 2.0); + foreach (Area tabletArea in config.TabletAreas) + { + tabletArea.X = config.TabletFullArea.X; + tabletArea.Y = config.TabletFullArea.Y + offsetY; + tabletArea.Width = config.TabletFullArea.Width / 2.0; + tabletArea.Height = config.TabletFullArea.Height / 2.0; + offsetY += 10.0; + + } + LoadSettingsFromConfiguration(); + } + } + + + } + + + // + // Canvas context menu opening + // + private void CanvasArea_ContextMenuOpening(object sender, ContextMenuEventArgs e) + { + + // + // Screen area canvas context menu + // + if (sender == canvasScreenMap) + { + if (!mouseArea.IsValid) + { + foreach (object item in canvasScreenMap.ContextMenu.Items) + ((UIElement)item).Visibility = Visibility.Collapsed; + menuCanvasResetScreenAreas.Visibility = Visibility.Visible; + } + else + { + // Find area index + int index = 0; + for (int i = 0; i < config.GetAreaCount(); i++) + { + if (config.ScreenAreas[i] == mouseArea.Area) index = i; + } + + // Create area information string + string areaInfo = "Area #" + (index + 1) + ": "; + areaInfo += Utils.GetNumberString(mouseArea.Area.Width, "0") + "x" + + Utils.GetNumberString(mouseArea.Area.Height, "0") + ", "; + areaInfo += Utils.GetNumberString(mouseArea.Area.Width / mouseArea.Area.Height, "0.000") + ":1"; + + menuCanvasScreenAreaInfo.Header = areaInfo; + foreach (object item in canvasScreenMap.ContextMenu.Items) + ((UIElement)item).Visibility = Visibility.Visible; + + if (index == 0) + { + menuCanvasRemoveScreenArea.Visibility = Visibility.Collapsed; + } + + + } + + // Hide add secondary area menu item + int areaCount = 0; + foreach (Area area in config.ScreenAreas) + { + if (area.IsEnabled) areaCount++; + } + if (areaCount < config.GetAreaCount()) + menuCanvasAddScreenArea.Visibility = Visibility.Visible; + else + menuCanvasAddScreenArea.Visibility = Visibility.Collapsed; + } + + // + // Tablet area canvas context menu + // + else if (sender == canvasTabletArea) + { + if (!mouseArea.IsValid) + { + foreach (object item in canvasTabletArea.ContextMenu.Items) + ((UIElement)item).Visibility = Visibility.Collapsed; + menuCanvasAddTabletArea.Visibility = Visibility.Visible; + menuCanvasResetTabletAreas.Visibility = Visibility.Visible; + + } + else + { + // Find area index + int index = 0; + for (int i = 0; i < config.GetAreaCount(); i++) + { + if (config.TabletAreas[i] == mouseArea.Area) index = i; + } + + // Create area information string + string areaInfo = "Area #" + (index + 1) + ": "; + areaInfo += Utils.GetNumberString(mouseArea.Area.Width) + "mm x " + + Utils.GetNumberString(mouseArea.Area.Height) + "mm, "; + areaInfo += Utils.GetNumberString(mouseArea.Area.Width / mouseArea.Area.Height, "0.000") + ":1"; + + menuCanvasTabletAreaInfo.Header = areaInfo; + + foreach (object item in canvasTabletArea.ContextMenu.Items) + ((UIElement)item).Visibility = Visibility.Visible; + + if (index == 0) + { + menuCanvasRemoveTabletArea.Visibility = Visibility.Collapsed; + } + } + + // Hide add secondary area menu item + int areaCount = 0; + foreach (Area area in config.ScreenAreas) + { + if (area.IsEnabled) areaCount++; + } + if (areaCount < config.GetAreaCount()) + menuCanvasAddTabletArea.Visibility = Visibility.Visible; + else + menuCanvasAddTabletArea.Visibility = Visibility.Collapsed; + + } + + } + + + + #region Wacom / Draw area + + // + // Wacom Area + // + private void ButtonWacomArea_Click(object sender, RoutedEventArgs e) + { + WindowWacomArea wacom = new WindowWacomArea(config, config.SelectedTabletArea); + wacom.ShowDialog(); + + // Set button clicked + if (wacom.DialogResult == true) + { + LoadSettingsFromConfiguration(); + } + } + + + // + // Draw area + // + private void ButtonDrawArea_Click(object sender, RoutedEventArgs e) + { + if (!isEnabledMeasurementToArea) + { + isEnabledMeasurementToArea = true; + driver.SendCommand("Measure 2"); + SetStatus("Click the top left and the bottom right corners of the area with your tablet pen!"); + buttonDrawArea.IsEnabled = false; + } + } + + #endregion + + } +} diff --git a/TabletDriverGUI/MainWindow.Console.cs b/TabletDriverGUI/MainWindow.Console.cs new file mode 100644 index 0000000..b9de986 --- /dev/null +++ b/TabletDriverGUI/MainWindow.Console.cs @@ -0,0 +1,506 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; + +namespace TabletDriverGUI +{ + public partial class MainWindow : Window + { + + // + // Console buffer to text + // + private void ConsoleBufferToText() + { + StringBuilder stringBuilder = new StringBuilder(); + + if (driver == null) return; + + // Lock console + driver.ConsoleLock(); + + // Get console status + if (!driver.HasConsoleUpdated) + { + driver.ConsoleUnlock(); + return; + } + driver.HasConsoleUpdated = false; + + // Create a string from buffer + foreach (string line in driver.ConsoleBuffer) + { + stringBuilder.Append(line); + stringBuilder.Append("\r\n"); + } + + // Unlock console + driver.ConsoleUnlock(); + + // Set output + textConsole.Text = stringBuilder.ToString(); + + // Scroll to end + scrollConsole.ScrollToEnd(); + + } + + + // + // Search text from rows + // + private List SearchRows(List rows, string search, int rowsBefore, int rowsAfter) + { + List buffer = new List(rowsBefore); + List output = new List(); + int rowCounter = 0; + + foreach (string row in rows) + { + if (row.Contains(search)) + { + if (buffer.Count > 0) + { + foreach (string bufferLine in buffer) + { + output.Add(bufferLine); + } + buffer.Clear(); + } + output.Add(row.Trim()); + rowCounter = rowsAfter; + } + else if (rowCounter > 0) + { + output.Add(row.Trim()); + rowCounter--; + } + else + { + buffer.Add(row); + if (buffer.Count > rowsBefore) + { + buffer.RemoveAt(0); + } + } + } + return output; + } + + + // + // Send a command to driver + // + private void ConsoleSendCommand(string line) + { + if (commandHistory.Last() != line) + { + commandHistory.Add(line); + } + commandHistoryIndex = commandHistory.Count(); + textConsoleInput.Text = ""; + textConsoleInput.ScrollToEnd(); + try + { + driver.SendCommand(line); + } + catch (Exception e) + { + driver.ConsoleAddLine("Error! " + e.Message); + } + } + + + // + // Console update timer tick + // + private void TimerConsoleUpdate_Tick(object sender, EventArgs e) + { + + // Update console text if console tab is active + if (tabControl.SelectedItem == tabConsole && WindowState != WindowState.Minimized) + { + ConsoleBufferToText(); + } + } + + + // + // Console input key down + // + private void TextConsoleInput_KeyDown(object sender, KeyEventArgs e) + { + if (e.Key == Key.Enter) + { + string line = textConsoleInput.Text; + ConsoleSendCommand(line.Trim()); + } + } + + + // + // Console input preview key down + // + private void TextConsoleInput_PreviewKeyDown(object sender, KeyEventArgs e) + { + + // + // Command tab complete + // + if (e.Key == Key.Tab + || + (e.Key == Key.Space && e.KeyboardDevice.Modifiers == ModifierKeys.Control) + ) + { + string fill; + string inputText = textConsoleInput.Text.Trim().ToLower(); + if (inputText.StartsWith("help ")) + { + fill = driver.CompleteCommandName(inputText.Substring(5), true); + if (fill != null) + textConsoleInput.Text = "Help " + fill; + } + else + { + fill = driver.CompleteCommandName(inputText, true); + if (fill != null) + textConsoleInput.Text = fill; + } + if (fill != null) + textConsoleInput.CaretIndex = textConsoleInput.Text.Length; + ConsoleBufferToText(); + e.Handled = true; + } + + + // + // Up arrow + // + else if (e.Key == Key.Up) + { + commandHistoryIndex--; + if (commandHistoryIndex < 0) commandHistoryIndex = 0; + textConsoleInput.Text = commandHistory[commandHistoryIndex]; + textConsoleInput.CaretIndex = textConsoleInput.Text.Length; + } + + // + // Down arrow + // + else if (e.Key == Key.Down) + { + commandHistoryIndex++; + if (commandHistoryIndex > commandHistory.Count() - 1) + { + commandHistoryIndex = commandHistory.Count(); + textConsoleInput.Text = ""; + } + else + { + textConsoleInput.Text = commandHistory[commandHistoryIndex]; + textConsoleInput.CaretIndex = textConsoleInput.Text.Length; + } + } + } + + + // + // Commands textbox key down + // + private void TextCommands_PreviewKeyDown(object sender, KeyEventArgs e) + { + + // + // Control + space command completion + // + if ( + e.Key == Key.Space && e.KeyboardDevice.Modifiers == ModifierKeys.Control + ) + { + TextBox textBoxSender = (TextBox)sender; + + string commandName = ""; + string text = textBoxSender.Text; + + int caretIndex = textBoxSender.CaretIndex; + if (caretIndex >= text.Length) + caretIndex = text.Length - 1; + if (caretIndex < 0) caretIndex = 0; + + int startIndex = caretIndex; + int endIndex = caretIndex; + + // Find word start index + for (startIndex = caretIndex; startIndex > 0; startIndex--) + { + // Find space or new line + if (text[startIndex] == '\r' || text[startIndex] == '\n' || text[startIndex] == ' ') + { + if (caretIndex == startIndex) continue; + startIndex++; + break; + } + } + + // Find word end index + caretIndex = caretIndex - 1; + if (caretIndex < 0) caretIndex = 0; + for (endIndex = caretIndex; endIndex < text.Length; endIndex++) + { + // Find space or new line + if (text[endIndex] == '\r' || text[endIndex] == '\n' || text[endIndex] == ' ') + { + if (caretIndex == endIndex) continue; + break; + } + } + + // Select word + textBoxSender.SelectionStart = startIndex; + textBoxSender.SelectionLength = endIndex - startIndex; + + // Command name completion + commandName = textBoxSender.SelectedText.Trim(); + string completedCommand = driver.CompleteCommandName(commandName, false); + string newText = text; + + // Set selected text as completed command name + if (completedCommand != null) + { + + // + // Close old tool tip + // + if (textBoxSender.ToolTip != null) + { + ((ToolTip)textBoxSender.ToolTip).IsOpen = false; + ((ToolTip)textBoxSender.ToolTip).IsEnabled = false; + } + textBoxSender.SelectedText = completedCommand; + + // Find commands + string foundCommands = "Commands:\n"; + int commandCount = 1; + foreach (var command in driver.Commands) + { + if (command.Key.ToLower().StartsWith(completedCommand.ToLower())) + { + foundCommands += command.Value + " "; + if (commandCount % 10 == 0) foundCommands += "\n"; + } + + commandCount++; + } + + // + // Create tool tip + // + ToolTip toolTip = new ToolTip + { + Placement = System.Windows.Controls.Primitives.PlacementMode.Relative, + PlacementTarget = textBoxSender, + HorizontalOffset = 100 + }; + toolTip.Opened += async delegate (object obj1, RoutedEventArgs eventArgs1) + { + toolTip.Content = foundCommands; + await Task.Delay(3000); + toolTip.IsOpen = false; + toolTip.IsEnabled = false; + textBoxSender.ToolTip = null; + }; + toolTip.IsOpen = true; + textBoxSender.ToolTip = toolTip; + + } + + // Set cursor position + textBoxSender.CaretIndex = textBoxSender.SelectionStart + textBoxSender.SelectionLength; + + // Reset selection + textBoxSender.SelectionLength = 0; + + e.Handled = true; + } + } + + + // + // Console output context menu + // + private void ConsoleMenuClick(object sender, RoutedEventArgs e) + { + + + // Copy all + if (sender == menuCopyAll) + { + Clipboard.SetText(textConsole.Text); + SetStatus("Console output copied to clipboard"); + } + + // Copy debug messages + else if (sender == menuCopyDebug) + { + string clipboard = ""; + List rows; + driver.ConsoleLock(); + rows = SearchRows(driver.ConsoleBuffer, "[DEBUG]", 0, 0); + driver.ConsoleUnlock(); + foreach (string row in rows) + clipboard += row + "\r\n"; + Clipboard.SetText(clipboard); + SetStatus("Debug message copied to clipboard"); + } + + // Copy error messages + else if (sender == menuCopyErrors) + { + string clipboard = ""; + List rows; + driver.ConsoleLock(); + rows = SearchRows(driver.ConsoleBuffer, "[ERROR]", 1, 1); + driver.ConsoleUnlock(); + foreach (string row in rows) + clipboard += row + "\r\n"; + Clipboard.SetText(clipboard); + SetStatus("Error message copied to clipboard"); + } + + // Start debug log + else if (sender == menuStartDebug) + { + string logFilename = "debug_" + DateTime.Now.ToString("yyyy-MM-dd_HH_mm_ss") + ".txt"; + ConsoleSendCommand("log " + logFilename); + ConsoleSendCommand("debug 1"); + } + + // Stop debug log + else if (sender == menuStopDebug) + { + ConsoleSendCommand("log off"); + ConsoleSendCommand("debug 0"); + } + + // Show latest debug log + else if (sender == menuFindLatestDebugLog) + { + try + { + var files = Directory.GetFiles(".", "debug_*.txt").OrderBy(a => File.GetCreationTime(a)); + if (files.Count() > 0) + { + string file = files.Last().ToString(); + Process.Start("explorer.exe", "/n /e,/select," + file); + } + } + catch (Exception) + { + } + } + + // Run benchmark + else if (sender == menuRunBenchmark) + { + ConsoleSendCommand("Benchmark"); + } + + // Copy Benchmark + else if (sender == menuCopyBenchmark) + { + string clipboard = ""; + List rows; + driver.ConsoleLock(); + rows = SearchRows(driver.ConsoleBuffer, " [STATUS] BENCHMARK ", 0, 0); + driver.ConsoleUnlock(); + foreach (string row in rows) + { + Match m = Regex.Match(row, " BENCHMARK ([0-9\\.]+) ([0-9\\.]+) ([0-9\\.]+) (.*)$"); + if (m.Success) + { + string tabletName = m.Groups[4].ToString(); + string totalReports = m.Groups[1].ToString(); + string noiseWidth = m.Groups[2].ToString(); + string noiseHeight = m.Groups[3].ToString(); + clipboard = + "Tablet(" + tabletName + ") " + + "Noise(" + noiseWidth + " mm x " + noiseHeight + " mm) " + + "Reports(" + totalReports + ")\r\n"; + } + } + + if (clipboard.Length > 0) + { + Clipboard.SetText(clipboard); + SetStatus("Benchmark result copied to clipboard"); + } + } + + + // Measure 2 points + else if (sender == menuMeasure2) + { + ConsoleSendCommand("Measure 2"); + } + + // Open startup log + else if (sender == menuFindStartupLog) + { + if (File.Exists("startuplog.txt")) + { + try { Process.Start("explorer.exe", "/n /e,/select,startuplog.txt"); } catch (Exception) { } + } + else + { + MessageBox.Show( + "Startup log not found!\n" + + "Make sure that it is possible to create and edit files in the '" + Directory.GetCurrentDirectory() + "' directory.\n", + "Error!", MessageBoxButton.OK, MessageBoxImage.Error + ); + } + } + + // Open driver folder + else if (sender == menuOpenFolder) + { + try { Process.Start("."); } catch (Exception) { } + } + + // Open GitHub page + else if (sender == menuOpenGithub) + { + try { Process.Start("https://github.com/hawku/TabletDriver"); } catch (Exception) { } + } + + // Open Latest URL + else if (sender == menuOpenLatestURL) + { + Regex regex = new Regex("(http[s]?://.+?)($|\\s)", RegexOptions.IgnoreCase | RegexOptions.Multiline); + MatchCollection matches = regex.Matches(textConsole.Text); + if (matches.Count > 0) + { + string url = matches[matches.Count - 1].Groups[0].ToString().Trim(); + try { Process.Start(url); } catch (Exception) { } + } + } + + // Report a problem + else if (sender == menuReportProblem) + { + try { Process.Start("https://github.com/hawku/TabletDriver/wiki/FAQ"); } catch (Exception) { } + } + + + } + + + } +} diff --git a/TabletDriverGUI/MainWindow.Driver.cs b/TabletDriverGUI/MainWindow.Driver.cs new file mode 100644 index 0000000..f9940dd --- /dev/null +++ b/TabletDriverGUI/MainWindow.Driver.cs @@ -0,0 +1,717 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; + +namespace TabletDriverGUI +{ + public partial class MainWindow : Window + { + + // + // Start the driver + // + void StartDriver() + { + + if (running) return; + + // Try to start the driver + try + { + running = true; + + // Console timer + timerConsoleUpdate.Start(); + + // Pen position timer + //timerUpdatePenPositions.Start(); + + driver.Start(config.DriverPath, config.DriverArguments); + if (!driver.IsRunning) + { + SetStatus("Can't start the driver! Check the console!"); + driver.ConsoleAddLine("ERROR! Can't start the driver!"); + } + else + { + SetStatus("Driver starting..."); + } + } + + // Start failed + catch (Exception e) + { + SetStatus("Can't start the driver! Check the console!"); + driver.ConsoleAddLine("ERROR! Can't start the driver!\n " + e.Message); + } + } + + + // + // Stop the driver + // + void StopDriver() + { + if (!running) return; + running = false; + + //timerUpdatePenPositions.Stop(); + + driver.Stop(); + timerConsoleUpdate.Stop(); + } + + + // + // Send settings to the driver + // + private void SendSettingsToDriver() + { + if (!driver.IsRunning) return; + + // Clear setting commands list + settingCommands.Clear(); + + // + // Desktop size + // + settingCommands.Add("DesktopSize " + textDesktopWidth.Text + " " + textDesktopHeight.Text); + + + // + // Screen and tablet areas + // + int areaIndex = 0; + for (int i = 0; i < config.GetAreaCount(); i++) + { + if (config.ScreenAreas[i].IsEnabled) + { + // Screen area + settingCommands.Add("ScreenArea " + + Utils.GetNumberString(config.ScreenAreas[i].Width) + " " + Utils.GetNumberString(config.ScreenAreas[i].Height) + " " + + Utils.GetNumberString(config.ScreenAreas[i].X) + " " + Utils.GetNumberString(config.ScreenAreas[i].Y) + " " + + areaIndex + ); + + // Inverted tablet area + if (config.Invert) + { + settingCommands.Add("TabletArea " + + Utils.GetNumberString(config.TabletAreas[i].Width) + " " + + Utils.GetNumberString(config.TabletAreas[i].Height) + " " + + Utils.GetNumberString(config.TabletFullArea.Width - config.TabletAreas[i].X) + " " + + Utils.GetNumberString(config.TabletFullArea.Height - config.TabletAreas[i].Y) + " " + + areaIndex + ); + settingCommands.Add( + "Rotate " + Utils.GetNumberString(config.TabletAreas[0].Rotation + 180) + " " + + areaIndex + ); + + } + + // Normal tablet area + else + { + settingCommands.Add("TabletArea " + + Utils.GetNumberString(config.TabletAreas[i].Width) + " " + + Utils.GetNumberString(config.TabletAreas[i].Height) + " " + + Utils.GetNumberString(config.TabletAreas[i].X) + " " + + Utils.GetNumberString(config.TabletAreas[i].Y) + " " + + areaIndex + ); + settingCommands.Add( + "Rotate " + Utils.GetNumberString(config.TabletAreas[0].Rotation) + " " + + areaIndex + ); + } + areaIndex++; + } + + + + } + settingCommands.Add("ScreenMapCount " + areaIndex); + + + // + // Output Mode + // + switch (config.Mode) + { + case Configuration.OutputModes.Standard: + + // Windows 8, 8.1, 10 + if (VersionHelper.IsWindows8OrGreater()) + { + if (config.Positioning == Configuration.OutputPositioning.Absolute) + settingCommands.Add("OutputMode Absolute"); + else + settingCommands.Add("OutputMode Relative"); + } + + // Windows 7 + else + { + if (config.Positioning == Configuration.OutputPositioning.Absolute) + settingCommands.Add("OutputMode SendInputAbsolute"); + else + settingCommands.Add("OutputMode SendInputRelative"); + } + break; + + case Configuration.OutputModes.WindowsInk: + if (config.Positioning == Configuration.OutputPositioning.Absolute) + settingCommands.Add("OutputMode DigitizerAbsolute"); + else + settingCommands.Add("OutputMode DigitizerRelative"); + break; + case Configuration.OutputModes.Compatibility: + + // Windows 8, 8.1, 10 + if (VersionHelper.IsWindows8OrGreater()) + { + if (config.Positioning == Configuration.OutputPositioning.Absolute) + settingCommands.Add("OutputMode SendInputAbsolute"); + else + settingCommands.Add("OutputMode SendInputRelative"); + } + + // Windows 7 + else + { + if (config.Positioning == Configuration.OutputPositioning.Absolute) + settingCommands.Add("OutputMode Absolute"); + else + settingCommands.Add("OutputMode Relative"); + } + break; + default: + break; + } + + // + // Relative positioning sensitivity + // + settingCommands.Add("RelativeSensitivity " + + Utils.GetNumberString(config.ScreenAreas[0].Width / config.TabletAreas[0].Width) + + " " + + Utils.GetNumberString(config.ScreenAreas[0].Height / config.TabletAreas[0].Height) + ); + + + // + // Pen button map + // + if (config.DisableButtons) + { + settingCommands.Add("ClearButtonMap"); + } + else + { + settingCommands.Add("ClearButtonMap"); + int button = 1; + foreach (string key in config.ButtonMap) + { + settingCommands.Add("ButtonMap " + button + " \"" + key + "\""); + button++; + } + } + + + // + // Tablet button map + // + if (config.DisableTabletButtons) + { + settingCommands.Add("ClearAuxButtonMap"); + } + else + { + settingCommands.Add("ClearAuxButtonMap"); + int button = 1; + foreach (string key in config.TabletButtonMap) + { + if (key != "") + { + settingCommands.Add("AuxButtonMap " + button + " \"" + key + "\""); + } + button++; + } + } + + + // + // Pressure + // + settingCommands.Add("PressureSensitivity " + Utils.GetNumberString(config.PressureSensitivity)); + settingCommands.Add("PressureDeadzone " + + Utils.GetNumberString(config.PressureDeadzoneLow) + " " + + Utils.GetNumberString(config.PressureDeadzoneHigh) + ); + + + // + // Scroll + // + settingCommands.Add("ScrollSensitivity " + Utils.GetNumberString(config.ScrollSensitivity)); + settingCommands.Add("ScrollAcceleration " + Utils.GetNumberString(config.ScrollAcceleration)); + settingCommands.Add("ScrollStopCursor " + (config.ScrollStopCursor ? "true" : "false")); + settingCommands.Add("ScrollDrag " + (config.ScrollDrag ? "true" : "false")); + + + // + // Smoothing filter + // + if (config.SmoothingEnabled) + { + settingCommands.Add( + "Smoothing " + Utils.GetNumberString(config.SmoothingLatency) + " " + + (config.SmoothingOnlyWhenButtons ? "true" : "false") + ); + settingCommands.Add("FilterTimerInterval " + Utils.GetNumberString(config.SmoothingInterval)); + } + else + { + settingCommands.Add("Smoothing 0"); + settingCommands.Add("FilterTimerInterval 10"); + } + + + // + // Noise filter + // + if (config.NoiseFilterEnabled) + { + settingCommands.Add("Noise " + Utils.GetNumberString(config.NoiseFilterBuffer) + " " + Utils.GetNumberString(config.NoiseFilterThreshold)); + } + else + { + settingCommands.Add("Noise 0"); + } + + + // + // Anti-smoothing filter + // + if (config.AntiSmoothingEnabled) + { + settingCommands.Add( + "AntiSmoothing " + + (config.AntiSmoothingOnlyWhenHover ? "true" : "false") + " " + + Utils.GetNumberString(config.AntiSmoothingDragMultiplier) + ); + + Configuration.AntiSmoothingSetting[] settings; + settings = (Configuration.AntiSmoothingSetting[])config.AntiSmoothingSettings.Clone(); + + // Sort + Array.Sort(settings, (a, b) => + { + if (a.Velocity > b.Velocity) return 1; + if (b.Velocity > a.Velocity) return -1; + return 0; + }); + foreach (var setting in settings) + { + if (setting.Enabled) + { + settingCommands.Add("AntiSmoothingAdd " + + Utils.GetNumberString(setting.Velocity) + " " + + Utils.GetNumberString(setting.Shape, "0.000") + " " + + Utils.GetNumberString(setting.Compensation) + ); + } + } + + } + else + { + settingCommands.Add("AntiSmoothing off"); + } + + + // + // Debugging + // + if (config.DebuggingEnabled) + { + settingCommands.Add("Debug true"); + } + else + { + settingCommands.Add("Debug false"); + } + + + // + // Custom commands + // + if (config.CustomCommands.Length > 0) + { + foreach (string command in config.CustomCommands) + { + string tmp = command.Trim(); + if (tmp.Length > 0) + { + settingCommands.Add(tmp); + } + } + } + + + // + // Send commands to the driver + // + foreach (string command in settingCommands) + { + // Skip comments + if (command.StartsWith("#")) continue; + + driver.SendCommand(command); + } + + + // + // Write settings to usersettings.cfg + // + try + { + File.WriteAllLines("config\\usersettings.cfg", settingCommands.ToArray()); + } + catch (Exception) + { + } + + } + + + // + // Driver message received + // + private void OnDriverMessageReceived(object sender, TabletDriver.DriverEventArgs e) + { + //ConsoleAddText(e.Message); + } + + + // + // Driver error received + // + private void OnDriverErrorReceived(object sender, TabletDriver.DriverEventArgs e) + { + SetStatusWarning(e.Message); + } + + + // + // Driver status message received + // + private void OnDriverStatusReceived(object sender, TabletDriver.DriverEventArgs e) + { + string variableName = e.Message; + string parameters = e.Parameters; + Application.Current.Dispatcher.Invoke(() => + { + ProcessStatusMessage(variableName, parameters); + }); + } + // Process driver status message + private void ProcessStatusMessage(string variableName, string parameters) + { + + // + // Startup commands request + // + if(variableName == "startup_request") + { + SendStartupCommands(); + } + + + // + // Settings request + // + else if (variableName == "settings_request") + { + SendSettingsToDriver(); + } + + // + // Tablet Name + // + else if (variableName == "tablet") + { + string tabletName = parameters; + string title = "TabletDriverGUI - " + tabletName; + Regex regex = new Regex("\\([^\\)]+\\)"); + config.TabletName = regex.Replace(tabletName, ""); + Title = title; + + // Limit notify icon text length + if (tabletName.Length > 63) + { + notifyIcon.Text = tabletName.Substring(0, 63); + } + else + { + notifyIcon.Text = tabletName; + } + SetStatus("Connected to " + tabletName); + } + + + // + // Tablet width + // + else if (variableName == "width") + { + if (Utils.ParseNumber(parameters, out double val)) + { + config.TabletFullArea.Width = val; + config.TabletFullArea.X = val / 2.0; + if (isFirstStart) + { + config.TabletAreas[0].Width = config.TabletFullArea.Width; + config.TabletAreas[0].X = config.TabletFullArea.X; + FixTabletAreaDimensions(config.TabletAreas[0], config.ScreenAreas[0]); + SendSettingsToDriver(); + } + LoadSettingsFromConfiguration(); + UpdateSettingsToConfiguration(); + } + } + + + // + // Tablet height + // + else if (variableName == "height") + { + if (Utils.ParseNumber(parameters, out double val)) + { + config.TabletFullArea.Height = val; + config.TabletFullArea.Y = val / 2.0; + if (isFirstStart) + { + config.TabletAreas[0].Height = config.TabletFullArea.Height; + config.TabletAreas[0].Y = config.TabletFullArea.Y; + FixTabletAreaDimensions(config.TabletAreas[0], config.ScreenAreas[0]); + SendSettingsToDriver(); + } + LoadSettingsFromConfiguration(); + UpdateSettingsToConfiguration(); + + } + } + + + // + // Tablet measurement to tablet area + // + else if (variableName == "measurement" && isEnabledMeasurementToArea) + { + string[] stringValues = parameters.Split(' '); + int valueCount = stringValues.Count(); + if (valueCount >= 4) + { + double minimumX = 10000; + double minimumY = 10000; + double maximumX = -10000; + double maximumY = -10000; + for (int i = 0; i < valueCount; i += 2) + { + if ( + Utils.ParseNumber(stringValues[i], out double x) + && + Utils.ParseNumber(stringValues[i + 1], out double y) + ) + { + // Find limits + if (x > maximumX) maximumX = x; + if (x < minimumX) minimumX = x; + if (y > maximumY) maximumY = y; + if (y < minimumY) minimumY = y; + } + } + + double areaWidth = maximumX - minimumX; + double areaHeight = maximumY - minimumY; + double centerX = minimumX + areaWidth / 2.0; + double centerY = minimumY + areaHeight / 2.0; + + config.SelectedTabletArea.Width = areaWidth; + config.SelectedTabletArea.Height = areaHeight; + config.SelectedTabletArea.X = centerX; + config.SelectedTabletArea.Y = centerY; + LoadSettingsFromConfiguration(); + UpdateSettingsToConfiguration(); + + + } + isEnabledMeasurementToArea = false; + buttonDrawArea.IsEnabled = true; + SetStatus(""); + } + + + // + // Tablet buttons + // + else if (variableName == "aux_buttons") + { + if (Utils.ParseNumber(parameters, out double test)) + { + tabletButtonCount = (int)test; + if (tabletButtonCount > 0) + { + for (int i = 0; i < 16; i++) + { + GroupBox box = (GroupBox)wrapPanelTabletButtons.Children[i]; + if (i >= tabletButtonCount) + { + box.Visibility = Visibility.Collapsed; + } + else + { + box.Visibility = Visibility.Visible; + } + } + groupBoxTabletButtons.Visibility = Visibility.Visible; + + } + if (isFirstStart) + SendSettingsToDriver(); + } + + } + + // + // Driver started + // + else if (variableName == "started") + { + if (parameters.Trim() == "1" || parameters.Trim().ToLower() == "true") + { + isFirstStart = false; + } + } + } + + + // + // Driver Started + // + private void OnDriverStarted(object sender, EventArgs e) + { + + } + + private void SendStartupCommands() + { + // Debugging commands + if (config.DebuggingEnabled) + { + driver.SendCommand("HIDList"); + } + + driver.SendCommand("GetCommands"); + driver.SendCommand("Echo"); + driver.SendCommand("Echo Driver version: " + Version); + try { driver.SendCommand("echo Windows version: " + Environment.OSVersion.VersionString); } catch (Exception) { } + try + { + driver.SendCommand("Echo Windows product: " + + Microsoft.Win32.Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "ProductName", "").ToString()); + driver.SendCommand("Echo Windows release: " + + Microsoft.Win32.Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "ReleaseId", "").ToString()); + } + catch (Exception) + { + } + driver.SendCommand("Echo"); + driver.SendCommand("CheckTablet"); + SendSettingsToDriver(); + driver.SendCommand("Info"); + driver.SendCommand("Start"); + driver.SendCommand("Log Off"); + driver.SendCommand("LogDirect False"); + driver.SendCommand("Echo"); + driver.SendCommand("Echo Driver started!"); + driver.SendCommand("Echo"); + } + + + // + // Driver Stopped + // + private void OnDriverStopped(object sender, EventArgs e) + { + if (running) + { + + // Automatic restart? + if (config.AutomaticRestart) + { + SetStatus("Driver stopped. Restarting! Check console !!!"); + driver.ConsoleAddLine("Driver stopped. Restarting!"); + + // Run in the main application thread + Application.Current.Dispatcher.Invoke(() => + { + driver.Stop(); + timerRestart.Start(); + }); + + } + else + { + SetStatus("Driver stopped!"); + driver.ConsoleAddLine("Driver stopped!"); + } + + // Run in the main application thread + Application.Current.Dispatcher.Invoke(() => + { + Title = "TabletDriverGUI"; + notifyIcon.Text = "No tablet found"; + groupBoxTabletButtons.Visibility = Visibility.Collapsed; + }); + + } + } + + + // + // Driver restart timer tick + // + private void TimerRestart_Tick(object sender, EventArgs e) + { + if (running) + { + driver.Start(config.DriverPath, config.DriverArguments); + } + timerRestart.Stop(); + } + + + // + // Restart Driver button click + // + private void RestartDriverClick(object sender, RoutedEventArgs e) + { + if (running) + { + StopDriver(); + } + StartDriver(); + } + + + } +} diff --git a/TabletDriverGUI/MainWindow.Ink.cs b/TabletDriverGUI/MainWindow.Ink.cs new file mode 100644 index 0000000..cbc4fcb --- /dev/null +++ b/TabletDriverGUI/MainWindow.Ink.cs @@ -0,0 +1,214 @@ +using Microsoft.Win32; +using System; +using System.IO; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Ink; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace TabletDriverGUI +{ + public partial class MainWindow : Window + { + + // Ink canvas undo history + StrokeCollection inkCanvasUndoHistory; + + // Ink canvas DrawingAttributes + DrawingAttributes inkCanvasDrawingAttributes; + + + // + // Ink canvas stylys move + // + private void InkCanvas_StylusMove(object sender, StylusEventArgs e) + { + double pressure = 0; + int count = 0; + StylusPointCollection points = e.GetStylusPoints(inkCanvas); + foreach (var point in points) + { + pressure += point.PressureFactor; + count++; + } + progressPressure.Value = pressure / count; + } + + + // + // Ink canvas stylus up + // + private void InkCanvas_StylusUp(object sender, StylusEventArgs e) + { + progressPressure.Value = 0; + + Random random = new Random(); + double shade = random.Next(0x33, 0x77); + inkCanvasDrawingAttributes.Color = Color.FromRgb( + (byte)(shade * (0.95 + random.NextDouble() * 0.1)), + (byte)(shade * (0.95 + random.NextDouble() * 0.1)), + (byte)(shade * (0.95 + random.NextDouble() * 0.1)) + ); + if (inkCanvasUndoHistory != null && inkCanvasUndoHistory.Count > 0) + { + inkCanvasUndoHistory.Clear(); + } + } + + + // + // Ink canvas key down + // + private void InkCanvas_KeyDown(object sender, KeyEventArgs e) + { + // Ctrl + Z undo + if (e.Key == Key.Z && e.KeyboardDevice.Modifiers == ModifierKeys.Control) + { + + ButtonInkUndo_Click(sender, null); + } + + // Ctrl + Y redo + if (e.Key == Key.Y && e.KeyboardDevice.Modifiers == ModifierKeys.Control) + { + ButtonInkRedo_Click(sender, null); + } + + } + + + // + // Windows Ink pressure sensitivity changed + // + private void SliderPressure_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) + { + sliderPressureSensitivity.ToolTip = Utils.GetNumberString(-sliderPressureSensitivity.Value); + sliderPressureDeadzoneLow.ToolTip = Utils.GetNumberString(sliderPressureDeadzoneLow.Value * 100) + "%"; + sliderPressureDeadzoneHigh.ToolTip = Utils.GetNumberString(sliderPressureDeadzoneHigh.Value * 100) + "%"; + + if (isLoadingSettings) return; + + config.PressureSensitivity = sliderPressureSensitivity.Value; + config.PressureDeadzoneLow = sliderPressureDeadzoneLow.Value; + config.PressureDeadzoneHigh = sliderPressureDeadzoneHigh.Value; + + + driver.SendCommand("PressureSensitivity " + Utils.GetNumberString(config.PressureSensitivity)); + driver.SendCommand("PressureDeadzone " + + Utils.GetNumberString(config.PressureDeadzoneLow) + " " + + Utils.GetNumberString(config.PressureDeadzoneHigh) + ); + } + + + // + // Clear ink canvas + // + private void ButtonInkClear_Click(object sender, RoutedEventArgs e) + { + inkCanvas.Strokes.Clear(); + inkCanvasUndoHistory.Clear(); + } + + + // + // Undo ink canvas + // + private void ButtonInkUndo_Click(object sender, RoutedEventArgs e) + { + if (inkCanvas.Strokes.Count > 0) + { + inkCanvasUndoHistory.Add(inkCanvas.Strokes[inkCanvas.Strokes.Count - 1]); + inkCanvas.Strokes.RemoveAt(inkCanvas.Strokes.Count - 1); + } + } + + + // + // Redo ink canvas + // + private void ButtonInkRedo_Click(object sender, RoutedEventArgs e) + { + if (inkCanvasUndoHistory.Count > 0) + { + inkCanvas.Strokes.Add(inkCanvasUndoHistory.Last()); + inkCanvasUndoHistory.RemoveAt(inkCanvasUndoHistory.Count - 1); + } + } + + + // + // Save ink canvas + // + private void ButtonInkSave_Click(object sender, RoutedEventArgs e) + { + SaveFileDialog fileDialog = new SaveFileDialog + { + DefaultExt = ".png", + Filter = "PNG Files (*.png)|*.png", + FileName = "inktest_" + DateTime.Now.ToString("yyyy-MM-dd_HHmmss") + ".png" + }; + + // File selection OK? + if (fileDialog.ShowDialog() == true) + { + try + { + RenderInkCanvasToPNG(inkCanvas, 2.0, fileDialog.FileName); + } + catch (Exception ex) + { + MessageBox.Show("Saving failed!\n" + ex.Message, "ERROR!", MessageBoxButton.OK, MessageBoxImage.Error); + } + } + } + + + // + // Render Ink canvas to PNG + // + private void RenderInkCanvasToPNG(InkCanvas canvas, double scale, string filepath) + { + double inkWidth = canvas.ActualWidth * scale; + double inkHeight = canvas.ActualHeight * scale; + + // Draw ink canvas to drawing visual + DrawingVisual drawingVisual = new DrawingVisual(); + using (DrawingContext context = drawingVisual.RenderOpen()) + { + VisualBrush visualBrush = new VisualBrush(canvas); + Brush borderBrush = new SolidColorBrush(inkCanvas.DefaultDrawingAttributes.Color); + Pen borderPen = new Pen(borderBrush, 5); + context.DrawRectangle(visualBrush, borderPen, new Rect(0, 0, inkWidth, inkHeight)); + } + + // Render drawing visual to a bitmap + RenderTargetBitmap targetBitmap = new RenderTargetBitmap( + (int)inkWidth, + (int)inkHeight, + 96, 96, + PixelFormats.Default + ); + targetBitmap.Render(drawingVisual); + + // Encode bitmap to PNG + PngBitmapEncoder encoder = new PngBitmapEncoder(); + encoder.Frames.Add(BitmapFrame.Create(targetBitmap)); + + // Save PNG to a file + FileStream fileStream = File.Open(filepath, FileMode.OpenOrCreate); + encoder.Save(fileStream); + + // Close the file + fileStream.Close(); + + encoder = null; + targetBitmap = null; + drawingVisual = null; + } + + } +} diff --git a/TabletDriverGUI/MainWindow.Settings.cs b/TabletDriverGUI/MainWindow.Settings.cs new file mode 100644 index 0000000..5ad54af --- /dev/null +++ b/TabletDriverGUI/MainWindow.Settings.cs @@ -0,0 +1,1473 @@ +using Microsoft.Win32; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Threading; + +namespace TabletDriverGUI +{ + public partial class MainWindow : Window + { + private CheckBox[] checkboxAntiSmoothingSettingEnabled; + private TextBox[] textAntiSmoothingVelocity; + private TextBox[] textAntiSmoothingShape; + private TextBox[] textAntiSmoothingCompensation; + + List presetsNoiseReduction; + List presetsAntiSmoothing; + + WindowTabletView tabletView; + + + // + // Create setting UI components + // + private void CreateSettingElements() + { + + // + // Create tablet button map WrapPanel items + // + for (int i = 0; i < 16; i++) + { + GroupBox groupBox = new GroupBox + { + Width = 90, + Header = "Button " + (i + 1).ToString() + }; + Button button = new Button + { + Height = 22, + Content = "", + Padding = new Thickness(2, 0, 2, 0), + ToolTip = "Empty", + Background = Brushes.White + }; + button.Click += ButtonMap_Click; + button.ToolTipOpening += ButtonMap_ToolTipOpening; + + groupBox.Content = button; + wrapPanelTabletButtons.Children.Add(groupBox); + } + CheckBox checkBox = new CheckBox + { + Content = "Disable buttons" + }; + checkBox.Checked += CheckboxChanged; + checkBox.Unchecked += CheckboxChanged; + checkBox.VerticalAlignment = VerticalAlignment.Bottom; + checkBox.Margin = new Thickness(5, 5, 5, 10); + wrapPanelTabletButtons.Children.Add(checkBox); + + + + // + // Smoothing rate ComboBox + // + comboBoxSmoothingRate.Items.Clear(); + for (int i = 1; i <= 8; i++) + { + comboBoxSmoothingRate.Items.Add((1000.0 / i).ToString("0") + " Hz"); + } + comboBoxSmoothingRate.SelectedIndex = 3; + + + + // + // Noise reduction presets + // + + presetsNoiseReduction = new List + { + new Configuration.Preset("Wacom 470/471", (conf) => + { + conf.NoiseFilterBuffer = 10; + conf.NoiseFilterThreshold = 0.4; + }), + new Configuration.Preset("Wacom 472", (conf) => + { + conf.NoiseFilterBuffer = 10; + conf.NoiseFilterThreshold = 0.3; + }), + new Configuration.Preset("Wacom 480", (conf) => + { + conf.NoiseFilterBuffer = 10; + conf.NoiseFilterThreshold = 0.5; + }), + }; + comboBoxNoiseReductionPresets.Items.Clear(); + foreach (var preset in presetsNoiseReduction) + { + comboBoxNoiseReductionPresets.Items.Add(preset.Name); + } + comboBoxNoiseReductionPresets.SelectionChanged += (sender, e) => + { + int index = comboBoxNoiseReductionPresets.SelectedIndex; + if (index >= 0 && index < presetsNoiseReduction.Count) + { + Configuration.Preset preset = presetsNoiseReduction[comboBoxNoiseReductionPresets.SelectedIndex]; + preset.Action(config); + LoadSettingsFromConfiguration(); + SetStatus("Noise reduction filter preset '" + preset.Name + "' loaded!"); + } + }; + + + + + + + + // + // Anti-smoothing presets + // + presetsAntiSmoothing = new List + { + new Configuration.Preset("Clear", (conf) => { }), + new Configuration.Preset("Simple", (conf) => + { + conf.SetAntiSmoothingSetting(0, true, 0, 0.5, 10); + }), + + new Configuration.Preset("Gaomon S56K", (conf) => + { + conf.SetAntiSmoothingSetting(0, true, 0, 0.1, 10); + }), + + new Configuration.Preset("Huion 420", (conf) => + { + conf.SetAntiSmoothingSetting(0, true, 0, 0.1, 15); + }), + + new Configuration.Preset("Huion H420", (conf) => + { + conf.SetAntiSmoothingSetting(0, true, 0, 0.1, 15); + }), + + new Configuration.Preset("Huion H430P", (conf) => + { + conf.SetAntiSmoothingSetting(0, true, 0, 0.1, 50); + conf.SetAntiSmoothingSetting(1, true, 70, 0.1, 30); + conf.SetAntiSmoothingSetting(2, true, 150, 0.1, 20); + }), + + new Configuration.Preset("Huion H640P", (conf) => + { + conf.SetAntiSmoothingSetting(0, true, 0, 0.1, 50); + conf.SetAntiSmoothingSetting(1, true, 70, 0.1, 30); + conf.SetAntiSmoothingSetting(2, true, 150, 0.1, 20); + }), + + new Configuration.Preset("VEIKK A50", (conf) => + { + conf.SetAntiSmoothingSetting(0, true, 0, 0.1, 10); + }), + + + new Configuration.Preset("VEIKK S640", (conf) => + { + conf.SetAntiSmoothingSetting(0, true, 0, 0.1, 0); + conf.SetAntiSmoothingSetting(1, true, 50, 1.5, 5); + conf.SetAntiSmoothingSetting(2, true, 100, 1.5, 10); + }), + + new Configuration.Preset("Wacom 490 Hover", (conf) => + { + conf.SetAntiSmoothingSetting(0, true, 0, 0.1, 25); + conf.SetAntiSmoothingSetting(1, true, 100, 0.1, 20); + conf.AntiSmoothingOnlyWhenHover = true; + }), + + new Configuration.Preset("XP-Pen G430", (conf) => + { + conf.SetAntiSmoothingSetting(0, true, 0, 0.1, 0); + conf.SetAntiSmoothingSetting(1, true, 50, 1.5, 5); + conf.SetAntiSmoothingSetting(2, true, 200, 1.5, 10); + }), + + new Configuration.Preset("XP-Pen G640", (conf) => + { + conf.SetAntiSmoothingSetting(0, true, 0, 0.1, 0); + conf.SetAntiSmoothingSetting(1, true, 50, 1.5, 5); + conf.SetAntiSmoothingSetting(2, true, 200, 1.5, 10); + }), + + }; + + + // + // Anti-smoothing preset combobox + // + comboBoxAntiSmoothingPresets.Items.Clear(); + foreach (var preset in presetsAntiSmoothing) + { + comboBoxAntiSmoothingPresets.Items.Add(preset.Name); + } + comboBoxAntiSmoothingPresets.SelectionChanged += (sender, e) => + { + int index = comboBoxAntiSmoothingPresets.SelectedIndex; + if (index >= 0 && index < presetsAntiSmoothing.Count) + { + Configuration.Preset preset = presetsAntiSmoothing[comboBoxAntiSmoothingPresets.SelectedIndex]; + config.AntiSmoothingOnlyWhenHover = false; + config.AntiSmoothingDragMultiplier = 1.0; + config.ClearAntiSmoothingSettings(); + preset.Action(config); + LoadSettingsFromConfiguration(); + SetStatus("Anti-smoothing filter preset '" + preset.Name + "' loaded!"); + } + }; + + + // + // Anti-smoothing filter settings + // + int antiSmoothingSettingCount = 5; + checkboxAntiSmoothingSettingEnabled = new CheckBox[antiSmoothingSettingCount]; + textAntiSmoothingVelocity = new TextBox[antiSmoothingSettingCount]; + textAntiSmoothingShape = new TextBox[antiSmoothingSettingCount]; + textAntiSmoothingCompensation = new TextBox[antiSmoothingSettingCount]; + + if (config.AntiSmoothingSettings.Length < antiSmoothingSettingCount) + { + Configuration.AntiSmoothingSetting[] newSettings = new Configuration.AntiSmoothingSetting[antiSmoothingSettingCount]; + for (int i = 0; i < antiSmoothingSettingCount; i++) + { + if (i < config.AntiSmoothingSettings.Length) + { + newSettings[i] = config.AntiSmoothingSettings[i]; + } + else + { + newSettings[i] = new Configuration.AntiSmoothingSetting + { + Enabled = false, + Velocity = 0, + Shape = 0.5, + Compensation = 0 + }; + } + } + config.AntiSmoothingSettings = newSettings; + } + + + // + // Loop through anti-smoothing settings + // + for (int i = 0; i < antiSmoothingSettingCount; i++) + { + + // Enabled + checkboxAntiSmoothingSettingEnabled[i] = new CheckBox + { + Width = 20, + Height = 25, + Margin = new Thickness(1), + VerticalContentAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Right + }; + checkboxAntiSmoothingSettingEnabled[i].Checked += CheckboxChanged; + checkboxAntiSmoothingSettingEnabled[i].Unchecked += CheckboxChanged; + stackPanelAntiSmoothingEnabled.Children.Add(checkboxAntiSmoothingSettingEnabled[i]); + + + // Velocity + Grid grid = new Grid(); + Label label = new Label { Content = "mm/s", HorizontalAlignment = HorizontalAlignment.Right }; + textAntiSmoothingVelocity[i] = new TextBox + { + Width = 80, + Height = 25, + Padding = new Thickness(2), + Margin = new Thickness(1) + }; + textAntiSmoothingVelocity[i].TextChanged += TextChanged; + textAntiSmoothingVelocity[i].DataContext = checkboxAntiSmoothingSettingEnabled[i]; + textAntiSmoothingVelocity[i].SetBinding(TextBox.IsEnabledProperty, "IsChecked"); + grid.Children.Add(textAntiSmoothingVelocity[i]); + grid.Children.Add(label); + stackPanelAntiSmoothingVelocity.Children.Add(grid); + + + // Shape + textAntiSmoothingShape[i] = new TextBox + { + Width = 60, + Height = 25, + Padding = new Thickness(2), + Margin = new Thickness(1) + }; + textAntiSmoothingShape[i].TextChanged += TextChanged; + textAntiSmoothingShape[i].DataContext = checkboxAntiSmoothingSettingEnabled[i]; + textAntiSmoothingShape[i].SetBinding(TextBox.IsEnabledProperty, "IsChecked"); + stackPanelAntiSmoothingShape.Children.Add(textAntiSmoothingShape[i]); + + + // Compensation + grid = new Grid(); + label = new Label { Content = "ms", HorizontalAlignment = HorizontalAlignment.Right }; + textAntiSmoothingCompensation[i] = new TextBox + { + Width = 100, + Height = 25, + Padding = new Thickness(2), + Margin = new Thickness(1) + }; + textAntiSmoothingCompensation[i].TextChanged += TextChanged; + textAntiSmoothingCompensation[i].DataContext = checkboxAntiSmoothingSettingEnabled[i]; + textAntiSmoothingCompensation[i].SetBinding(TextBox.IsEnabledProperty, "IsChecked"); + + grid.Children.Add(textAntiSmoothingCompensation[i]); + grid.Children.Add(label); + stackPanelAntiSmoothingCompensation.Children.Add(grid); + + } + + + // + // Tablet view window + // + tabletView = null; + + } + + + + #region Configuration Load, Update, Init + + // + // Load settings from configuration + // + private void LoadSettingsFromConfiguration() + { + if (isLoadingSettings) return; + isLoadingSettings = true; + + // + // Tablet area + // + textTabletAreaWidth.Text = Utils.GetNumberString(config.SelectedTabletArea.Width); + textTabletAreaHeight.Text = Utils.GetNumberString(config.SelectedTabletArea.Height); + textTabletAreaX.Text = Utils.GetNumberString(config.SelectedTabletArea.X); + textTabletAreaY.Text = Utils.GetNumberString(config.SelectedTabletArea.Y); + checkBoxForceAspect.IsChecked = config.ForceAspectRatio; + + + // + // Positioning & Mode + // + comboBoxOutputPositioning.SelectedIndex = (int)config.Positioning; + + switch (config.Mode) + { + case Configuration.OutputModes.Standard: + radioOutputModeStandard.IsChecked = true; + break; + case Configuration.OutputModes.WindowsInk: + radioOutputModeWindowsInk.IsChecked = true; + break; + case Configuration.OutputModes.Compatibility: + radioOutputModeCombatibility.IsChecked = true; + break; + default: + break; + } + + + + // + // Windows Ink pressure + // + if (config.Mode == Configuration.OutputModes.WindowsInk) + groupBoxWindowsInkSettings.IsEnabled = true; + else + groupBoxWindowsInkSettings.IsEnabled = false; + + + // + // Rotation + // + textTabletAreaRotation.Text = Utils.GetNumberString(config.TabletAreas[0].Rotation); + checkBoxInvert.IsChecked = config.Invert; + + // + // Desktop size + // + if (config.AutomaticDesktopSize) + { + textDesktopWidth.Text = Utils.GetNumberString(GetVirtualDesktopSize().Width); + textDesktopHeight.Text = Utils.GetNumberString(GetVirtualDesktopSize().Height); + config.DesktopSize.Width = GetVirtualDesktopSize().Width; + config.DesktopSize.Height = GetVirtualDesktopSize().Height; + config.DesktopSize.X = config.DesktopSize.Width / 2.0; + config.DesktopSize.Y = config.DesktopSize.Height / 2.0; + textDesktopWidth.IsEnabled = false; + textDesktopHeight.IsEnabled = false; + } + else + { + textDesktopWidth.Text = Utils.GetNumberString(config.DesktopSize.Width); + textDesktopHeight.Text = Utils.GetNumberString(config.DesktopSize.Height); + config.DesktopSize.X = config.DesktopSize.Width / 2.0; + config.DesktopSize.Y = config.DesktopSize.Height / 2.0; + } + checkBoxAutomaticDesktopSize.IsChecked = config.AutomaticDesktopSize; + + + // + // Screen area + // + textScreenAreaWidth.Text = Utils.GetNumberString(config.SelectedScreenArea.Width, "0"); + textScreenAreaHeight.Text = Utils.GetNumberString(config.SelectedScreenArea.Height, "0"); + textScreenAreaX.Text = Utils.GetNumberString(config.SelectedScreenArea.X - config.SelectedScreenArea.Width / 2.0, "0"); + textScreenAreaY.Text = Utils.GetNumberString(config.SelectedScreenArea.Y - config.SelectedScreenArea.Height / 2.0, "0"); + + + // + // Move screen areas to valid positions + // + for (int i = 0; i < config.GetAreaCount(); i++) + config.ScreenAreas[i].MoveInside(config.DesktopSize); + + textScreenAreaX.Text = Utils.GetNumberString(config.SelectedScreenArea.X - config.SelectedScreenArea.Width / 2.0); + textScreenAreaY.Text = Utils.GetNumberString(config.SelectedScreenArea.Y - config.SelectedScreenArea.Height / 2.0); + + + // + // Force aspect ratio + // + if (config.ForceAspectRatio) + { + for (int i = 0; i < config.GetAreaCount(); i++) + { + config.TabletAreas[i].Height = config.TabletAreas[i].Width / (config.ScreenAreas[i].Width / config.ScreenAreas[i].Height); + } + + textTabletAreaHeight.Text = Utils.GetNumberString(config.SelectedTabletArea.Height); + textTabletAreaHeight.IsEnabled = false; + } + + + // + // Move tablet areas to valid positions + // + for (int i = 0; i < config.GetAreaCount(); i++) + config.TabletAreas[i].MoveInside(config.TabletFullArea); + + textTabletAreaX.Text = Utils.GetNumberString(config.SelectedTabletArea.X); + textTabletAreaY.Text = Utils.GetNumberString(config.SelectedTabletArea.Y); + + + + // + // Pen buttons + // + if (config.ButtonMap.Count() == 3) + { + buttonPenButton1.Content = config.ButtonMap[0]; + buttonPenButton2.Content = config.ButtonMap[1]; + buttonPenButton3.Content = config.ButtonMap[2]; + } + else + { + config.ButtonMap = new string[] { "MOUSE1", "MOUSE2", "MOUSE3" }; + } + checkBoxDisableButtons.IsChecked = config.DisableButtons; + + + // + // Tablet buttons + // + if (config.TabletButtonMap.Count() == 16) + { + for (int i = 0; i < 16; i++) + { + GroupBox box = (GroupBox)wrapPanelTabletButtons.Children[i]; + Button button = (Button)(box.Content); + button.Content = config.TabletButtonMap[i]; + } + } + else + { + config.TabletButtonMap = new string[16]; + for (int i = 0; i < 16; i++) config.TabletButtonMap[i] = ""; + } + if (wrapPanelTabletButtons.Children.Count == 17) + { + ((CheckBox)wrapPanelTabletButtons.Children[16]).IsChecked = config.DisableTabletButtons; + } + + + // + // Pressure + // + sliderPressureSensitivity.Value = config.PressureSensitivity; + sliderPressureDeadzoneLow.Value = config.PressureDeadzoneLow; + sliderPressureDeadzoneHigh.Value = config.PressureDeadzoneHigh; + + + // + // Scroll + // + textScrollSensitivity.Text = Utils.GetNumberString(config.ScrollSensitivity); + textScrollAcceleration.Text = Utils.GetNumberString(config.ScrollAcceleration); + checkBoxScrollStopCursor.IsChecked = config.ScrollStopCursor; + checkBoxScrollDrag.IsChecked = config.ScrollDrag; + + + // + // Smoothing filter + // + checkBoxSmoothing.IsChecked = config.SmoothingEnabled; + textSmoothingLatency.Text = Utils.GetNumberString(config.SmoothingLatency); + comboBoxSmoothingRate.SelectedIndex = config.SmoothingInterval - 1; + checkBoxSmoothingOnlyWhenButtons.IsChecked = config.SmoothingOnlyWhenButtons; + + + // + // Noise filter + // + checkBoxNoiseFilter.IsChecked = config.NoiseFilterEnabled; + textNoiseBuffer.Text = Utils.GetNumberString(config.NoiseFilterBuffer); + textNoiseThreshold.Text = Utils.GetNumberString(config.NoiseFilterThreshold); + + // + // Anti-smoothing filter + // + checkBoxAntiSmoothing.IsChecked = config.AntiSmoothingEnabled; + checkBoxAntiSmoothingOnlyWhenHover.IsChecked = config.AntiSmoothingOnlyWhenHover; + textAntiSmoothingDragMultiplier.Text = Utils.GetNumberString(config.AntiSmoothingDragMultiplier); + for (int i = 0; i < config.AntiSmoothingSettings.Length; i++) + { + checkboxAntiSmoothingSettingEnabled[i].IsChecked = config.AntiSmoothingSettings[i].Enabled; + textAntiSmoothingVelocity[i].Text = Utils.GetNumberString(config.AntiSmoothingSettings[i].Velocity); + textAntiSmoothingShape[i].Text = Utils.GetNumberString(config.AntiSmoothingSettings[i].Shape, "0.000"); + textAntiSmoothingCompensation[i].Text = Utils.GetNumberString(config.AntiSmoothingSettings[i].Compensation); + } + + + // + // Automatic restart + // + checkBoxAutomaticRestart.IsChecked = config.AutomaticRestart; + + // + // Run at startup + // + checkBoxRunAtStartup.IsChecked = config.RunAtStartup; + + // + // Custom commands + // + string tmp = ""; + foreach (string command in config.CustomCommands) + { + if (command.Trim().Length > 0) + tmp += command.Trim() + "\n"; + } + textCustomCommands.Text = tmp; + + // + // Debugging + // + checkBoxDebugging.IsChecked = config.DebuggingEnabled; + + // Settings loaded + isLoadingSettings = false; + + // Update canvases + UpdateCanvasElements(); + + // Update binding data context + DataContext = null; + DataContext = config; + + } + + + // + // Update settings to configuration + // + private void UpdateSettingsToConfiguration() + { + if (isLoadingSettings) + return; + + bool oldValue; + + + // + // Tablet area + // + if (Utils.ParseNumber(textTabletAreaWidth.Text, out double value)) + config.SelectedTabletArea.Width = value; + if (Utils.ParseNumber(textTabletAreaHeight.Text, out value)) + config.SelectedTabletArea.Height = value; + if (Utils.ParseNumber(textTabletAreaX.Text, out value)) + config.SelectedTabletArea.X = value; + if (Utils.ParseNumber(textTabletAreaY.Text, out value)) + config.SelectedTabletArea.Y = value; + if (Utils.ParseNumber(textTabletAreaRotation.Text, out value)) + config.TabletAreas[0].Rotation = value; + + // Update secondary area rotations + for (int i = 1; i < config.GetAreaCount(); i++) + { + config.TabletAreas[i].Rotation = config.TabletAreas[0].Rotation; + } + + + config.Invert = (bool)checkBoxInvert.IsChecked; + config.ForceAspectRatio = (bool)checkBoxForceAspect.IsChecked; + + // + // Output positioning and mode + // + if (comboBoxOutputPositioning.SelectedIndex >= 0) + config.Positioning = (Configuration.OutputPositioning)comboBoxOutputPositioning.SelectedIndex; + else + config.Positioning = Configuration.OutputPositioning.Absolute; + + if (radioOutputModeStandard.IsChecked == true) config.Mode = Configuration.OutputModes.Standard; + else if (radioOutputModeWindowsInk.IsChecked == true) config.Mode = Configuration.OutputModes.WindowsInk; + else if (radioOutputModeCombatibility.IsChecked == true) config.Mode = Configuration.OutputModes.Compatibility; + + // + // Force the tablet areas to be inside of the full area + // + for (int i = 0; i < config.GetAreaCount(); i++) + config.TabletAreas[i].MoveInside(config.TabletFullArea); + + + // + // Screen area + // + if (Utils.ParseNumber(textScreenAreaWidth.Text, out value)) + config.SelectedScreenArea.Width = value; + if (Utils.ParseNumber(textScreenAreaHeight.Text, out value)) + config.SelectedScreenArea.Height = value; + if (Utils.ParseNumber(textScreenAreaX.Text, out value)) + config.SelectedScreenArea.X = value + config.SelectedScreenArea.Width / 2.0; + if (Utils.ParseNumber(textScreenAreaY.Text, out value)) + config.SelectedScreenArea.Y = value + config.SelectedScreenArea.Height / 2.0; + + + + // + // Desktop size + // + if (Utils.ParseNumber(textDesktopWidth.Text, out value)) + config.DesktopSize.Width = value; + if (Utils.ParseNumber(textDesktopHeight.Text, out value)) + config.DesktopSize.Height = value; + config.AutomaticDesktopSize = (bool)checkBoxAutomaticDesktopSize.IsChecked; + if (config.AutomaticDesktopSize == true) + { + textDesktopWidth.Text = Utils.GetNumberString(GetVirtualDesktopSize().Width); + textDesktopHeight.Text = Utils.GetNumberString(GetVirtualDesktopSize().Height); + config.DesktopSize.Width = GetVirtualDesktopSize().Width; + config.DesktopSize.Height = GetVirtualDesktopSize().Height; + } + + + // + // Force aspect ratio + // + if (config.ForceAspectRatio) + { + config.SelectedTabletArea.Height = config.SelectedTabletArea.Width / (config.SelectedScreenArea.Width / config.SelectedScreenArea.Height); + textTabletAreaHeight.Text = Utils.GetNumberString(config.SelectedTabletArea.Height); + } + + + // + // Button map + // + config.ButtonMap[0] = buttonPenButton1.Content.ToString(); + config.ButtonMap[1] = buttonPenButton2.Content.ToString(); + config.ButtonMap[2] = buttonPenButton3.Content.ToString(); + config.DisableButtons = (bool)checkBoxDisableButtons.IsChecked; + + + // + // Tablet button map + // + for (int i = 0; i < 16; i++) + { + GroupBox box = (GroupBox)wrapPanelTabletButtons.Children[i]; + Button button = (Button)(box.Content); + config.TabletButtonMap[i] = button.Content.ToString(); + } + if (wrapPanelTabletButtons.Children.Count == 17) + { + config.DisableTabletButtons = (bool)(((CheckBox)wrapPanelTabletButtons.Children[16]).IsChecked); + } + + + // + // Pressure sensitivity + // + config.PressureSensitivity = sliderPressureSensitivity.Value; + config.PressureDeadzoneLow = sliderPressureDeadzoneLow.Value; + config.PressureDeadzoneHigh = sliderPressureDeadzoneHigh.Value; + + + // + // Scroll + // + if (Utils.ParseNumber(textScrollSensitivity.Text, out value)) + config.ScrollSensitivity = value; + if (Utils.ParseNumber(textScrollAcceleration.Text, out value)) + config.ScrollAcceleration = value; + config.ScrollStopCursor = (bool)checkBoxScrollStopCursor.IsChecked; + config.ScrollDrag = (bool)checkBoxScrollDrag.IsChecked; + + + // + // Smoothing filter + // + config.SmoothingEnabled = (bool)checkBoxSmoothing.IsChecked; + config.SmoothingInterval = comboBoxSmoothingRate.SelectedIndex + 1; + if (Utils.ParseNumber(textSmoothingLatency.Text, out value)) + config.SmoothingLatency = value; + config.SmoothingOnlyWhenButtons = (bool)checkBoxSmoothingOnlyWhenButtons.IsChecked; + + + // + // Noise filter + // + config.NoiseFilterEnabled = (bool)checkBoxNoiseFilter.IsChecked; + if (Utils.ParseNumber(textNoiseBuffer.Text, out value)) + config.NoiseFilterBuffer = (int)value; + if (Utils.ParseNumber(textNoiseThreshold.Text, out value)) + config.NoiseFilterThreshold = value; + + + // + // Anti-smoothing filter + // + config.AntiSmoothingEnabled = (bool)checkBoxAntiSmoothing.IsChecked; + config.AntiSmoothingOnlyWhenHover = (bool)checkBoxAntiSmoothingOnlyWhenHover.IsChecked; + if (Utils.ParseNumber(textAntiSmoothingDragMultiplier.Text, out value)) + config.AntiSmoothingDragMultiplier = value; + + for (int i = 0; i < config.AntiSmoothingSettings.Length; i++) + { + config.AntiSmoothingSettings[i].Enabled = (bool)checkboxAntiSmoothingSettingEnabled[i].IsChecked; + + if (Utils.ParseNumber(textAntiSmoothingVelocity[i].Text, out value)) + config.AntiSmoothingSettings[i].Velocity = value; + if (Utils.ParseNumber(textAntiSmoothingShape[i].Text, out value)) + config.AntiSmoothingSettings[i].Shape = value; + if (Utils.ParseNumber(textAntiSmoothingCompensation[i].Text, out value)) + config.AntiSmoothingSettings[i].Compensation = value; + + } + + + // + // Automatic restart + // + config.AutomaticRestart = (bool)checkBoxAutomaticRestart.IsChecked; + + + // + // Run at startup + // + oldValue = config.RunAtStartup; + config.RunAtStartup = (bool)checkBoxRunAtStartup.IsChecked; + if (config.RunAtStartup != oldValue) + SetRunAtStartup(config.RunAtStartup); + + + // + // Custom commands + // + List commandList = new List(); + foreach (string command in textCustomCommands.Text.Split('\n')) + if (command.Trim().Length > 0) + commandList.Add(command.Trim()); + config.CustomCommands = commandList.ToArray(); + + + // + // Debugging + // + config.DebuggingEnabled = (bool)checkBoxDebugging.IsChecked; + + + // Update canvases + UpdateCanvasElements(); + + } + + + // + // Initialize configuration + // + private void InitializeConfiguration() + { + isLoadingSettings = true; + Width = config.WindowWidth; + Height = config.WindowHeight; + isLoadingSettings = false; + + // + // Convert old configuration format area values + // + if (config.ScreenArea != null) + { + config.ScreenAreas[0].Width = config.ScreenArea.Width; + config.ScreenAreas[0].Height = config.ScreenArea.Height; + config.ScreenAreas[0].X = config.ScreenArea.X + config.ScreenArea.Width / 2.0; + config.ScreenAreas[0].Y = config.ScreenArea.Y + config.ScreenArea.Height / 2.0; + config.ScreenArea = null; + } + if (config.TabletArea != null) + { + config.TabletAreas[0].Set(config.TabletArea); + config.TabletArea = null; + } + + // + // Conversion from config format V1 + // + if (config.ConfigVersion <= 1) + { + + // + // Convert old button map + // + bool isOld = true; + if (config.ButtonMap.Length == 3) + { + for (int i = 0; i < 3; i++) + { + if (config.ButtonMap[i] != "1" && config.ButtonMap[i] != "2" && config.ButtonMap[i] != "3") + { + isOld = false; + } + } + if (isOld) + { + for (int i = 0; i < 3; i++) + config.ButtonMap[i] = "MOUSE" + config.ButtonMap[i].ToString(); + } + } + } + + // Fix screen area array length + if (config.ScreenAreas.Length != config.GetMaxAreaCount()) + { + Area[] newScreenAreas = new Area[config.GetMaxAreaCount()]; + for (int i = 0; i < config.GetMaxAreaCount(); i++) + { + if (i < config.ScreenAreas.Length) + { + newScreenAreas[i] = config.ScreenAreas[i]; + } + else + { + newScreenAreas[i] = new Area(0, 0, 0, 0); + } + } + config.ScreenAreas = newScreenAreas; + } + + // Fix tablet area array length + if (config.TabletAreas.Length != config.GetMaxAreaCount()) + { + Area[] newTabletAreas = new Area[config.GetMaxAreaCount()]; + for (int i = 0; i < config.GetMaxAreaCount(); i++) + { + if (i < config.TabletAreas.Length) + { + newTabletAreas[i] = config.TabletAreas[i]; + } + else + { + newTabletAreas[i] = new Area(0, 0, 0, 0); + } + } + config.TabletAreas = newTabletAreas; + + } + + + // Invalid screen area -> Set defaults + for (int i = 0; i < config.GetAreaCount(); i++) + { + if (config.ScreenAreas[i].Width == 0 || config.ScreenAreas[i].Height == 0) + { + config.DesktopSize.Width = GetVirtualDesktopSize().Width; + config.DesktopSize.Height = GetVirtualDesktopSize().Height; + config.ScreenAreas[i].Width = config.DesktopSize.Width; + config.ScreenAreas[i].Height = config.DesktopSize.Height; + config.ScreenAreas[i].X = config.DesktopSize.Width / 2.0; + config.ScreenAreas[i].Y = config.DesktopSize.Height / 2.0; + } + + FixTabletAreaDimensions(config.TabletAreas[i], config.ScreenAreas[i]); + } + + + // Primary area is always enabled + config.ScreenAreas[0].IsEnabled = true; + + // Set selected area to primary area + config.SelectedScreenArea = config.ScreenAreas[0]; + config.SelectedTabletArea = config.TabletAreas[0]; + + // Reset tablet name + config.TabletName = ""; + + // Load settings from configuration + LoadSettingsFromConfiguration(); + + // Update the settings back to the configuration + UpdateSettingsToConfiguration(); + + // Set run at startup + SetRunAtStartup(config.RunAtStartup); + + // Set window data context + this.DataContext = config; + + } + + + #endregion + + + + // + // Save settings + // + private void SaveSettings(object sender, RoutedEventArgs e) + { + try + { + config.Write(configFilename); + SendSettingsToDriver(); + + // + // Enable/Disable Windows Ink pressure settings + // + if (config.Mode == Configuration.OutputModes.WindowsInk) + groupBoxWindowsInkSettings.IsEnabled = true; + else + groupBoxWindowsInkSettings.IsEnabled = false; + + SetStatus("Settings saved!"); + } + catch (Exception) + { + string dir = Directory.GetCurrentDirectory(); + MessageBox.Show("Error occured while saving the configuration.\n" + + "Make sure that it is possible to create and edit files in the '" + dir + "' directory.\n", + "ERROR!", MessageBoxButton.OK, MessageBoxImage.Error + ); + } + } + + + // + // Set run at startup + // + private void SetRunAtStartup(bool enabled) + { + try + { + string path = System.Reflection.Assembly.GetExecutingAssembly().Location; + string entryName = "TabletDriverGUI"; + RegistryKey rk = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true); + if (enabled) + rk.SetValue(entryName, "\"" + path + "\" --hide"); + else + rk.DeleteValue(entryName, false); + + rk.Close(); + } + catch (Exception) + { + } + } + + + // + // TextBox setting changed + // + private void TextChanged(object sender, TextChangedEventArgs e) + { + UpdateSettingsToConfiguration(); + } + + + // + // Checkbox setting changed + // + private void CheckboxChanged(object sender, RoutedEventArgs e) + { + if (isLoadingSettings) return; + + // Disable tablet area height when aspect ratio is forced + if (checkBoxForceAspect.IsChecked == true) + textTabletAreaHeight.IsEnabled = false; + else + textTabletAreaHeight.IsEnabled = true; + + + // Disable desktop size settings when automatic is checked + if (checkBoxAutomaticDesktopSize.IsChecked == true) + { + textDesktopWidth.Text = Utils.GetNumberString(GetVirtualDesktopSize().Width); + textDesktopHeight.Text = Utils.GetNumberString(GetVirtualDesktopSize().Height); + textDesktopWidth.IsEnabled = false; + textDesktopHeight.IsEnabled = false; + } + else + { + textDesktopWidth.IsEnabled = true; + textDesktopHeight.IsEnabled = true; + } + + // Debugging checkbox + if (sender == checkBoxDebugging) + { + if (checkBoxDebugging.IsChecked == true) + { + driver.SendCommand("Debug true"); + } + else + { + driver.SendCommand("Debug false"); + } + } + + UpdateSettingsToConfiguration(); + + } + + + // + // Selection settings changed + // + private void ItemSelectionChanged(object sender, SelectionChangedEventArgs e) + { + UpdateSettingsToConfiguration(); + } + + + // + // Window size changed + // + private void MainWindow_SizeChanged(object sender, SizeChangedEventArgs e) + { + if (!IsLoaded || isLoadingSettings) return; + if (WindowState != WindowState.Maximized) + { + config.WindowWidth = (int)e.NewSize.Width; + config.WindowHeight = (int)e.NewSize.Height; + } + UpdateCanvasElements(); + } + + + // + // Monitor combobox clicked -> create new monitor list + // + private void ComboBoxMonitor_MouseDown(object sender, MouseButtonEventArgs e) + { + comboBoxMonitor.Items.Clear(); + + + System.Windows.Forms.Screen[] screens = GetAvailableScreens(); + if (screens.Length > 1) + { + comboBoxMonitor.Items.Add("Full desktop"); + foreach (System.Windows.Forms.Screen screen in screens) + { + string name = screen.DeviceName; + if (screen.Primary) + name += " Main"; + + comboBoxMonitor.Items.Add(name); + } + } + else + { + comboBoxMonitor.Items.Add(System.Windows.Forms.Screen.PrimaryScreen.DeviceName); + } + + } + + + // + // Monitor selected -> change screen map + // + private void ComboBoxMonitor_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (sender != comboBoxMonitor) return; + if (e.AddedItems.Count <= 0) return; + + System.Windows.Forms.Screen[] screens = GetAvailableScreens(); + Vector minimumScreenPosition = GetMinimumScreenPosition(screens); + + int index = comboBoxMonitor.SelectedIndex; + if (index == 0) + { + textScreenAreaX.Text = "0"; + textScreenAreaY.Text = "0"; + textScreenAreaWidth.Text = Utils.GetNumberString(config.DesktopSize.Width); + textScreenAreaHeight.Text = Utils.GetNumberString(config.DesktopSize.Height); + } + else if (index > 0) + { + index--; + + // Monitors + if (index >= 0 && index < screens.Length && screens.Length > 1) + { + textScreenAreaX.Text = Utils.GetNumberString(screens[index].Bounds.X - minimumScreenPosition.X); + textScreenAreaY.Text = Utils.GetNumberString(screens[index].Bounds.Y - minimumScreenPosition.Y); + textScreenAreaWidth.Text = Utils.GetNumberString(screens[index].Bounds.Width); + textScreenAreaHeight.Text = Utils.GetNumberString(screens[index].Bounds.Height); + } + + } + comboBoxMonitor.Text = ""; + comboBoxMonitor.SelectedIndex = -1; + UpdateSettingsToConfiguration(); + } + + + // + // Button mapping click + // + private void ButtonMap_Click(object sender, RoutedEventArgs e) + { + Button button = (Button)sender; + //MessageBox.Show(button.Content.ToString()); + + bool isPenButton = false; + + if (sender == buttonPenButton1) isPenButton = true; + else if (sender == buttonPenButton2) isPenButton = true; + else if (sender == buttonPenButton3) isPenButton = true; + + + WindowButtonMapping windowButtonMapping = new WindowButtonMapping(button, isPenButton); + windowButtonMapping.ShowDialog(); + if (windowButtonMapping.DialogResult == true) + { + button.Content = windowButtonMapping.Result.ToUpper(); + UpdateSettingsToConfiguration(); + } + + } + + + // + // Button map tooltip opening + // + private void ButtonMap_ToolTipOpening(object sender, ToolTipEventArgs e) + { + Button button = (Button)sender; + if (button.Content.ToString() == "") + { + button.ToolTip = "Empty"; + } + else + { + button.ToolTip = button.Content; + } + } + + + // + // Main Menu Click + // + private void MainMenuClick(object sender, RoutedEventArgs e) + { + + // + // Save settings + // + if (sender == mainMenuSaveSettings) + { + SaveSettings(sender, e); + } + + // + // Import settings + // + else if (sender == mainMenuImport) + { + OpenFileDialog dialog = new OpenFileDialog + { + InitialDirectory = Directory.GetCurrentDirectory(), + Filter = "XML File|*.xml", + Title = "Import settings" + }; + if (dialog.ShowDialog() == true) + { + try + { + Configuration tmpConfig = Configuration.CreateFromFile(dialog.FileName); + config = tmpConfig; + InitializeConfiguration(); + Application.Current.Dispatcher.Invoke(() => + { + LoadSettingsFromConfiguration(); + UpdateSettingsToConfiguration(); + }); + SetStatus("Settings imported!"); + + } + catch (Exception) + { + MessageBox.Show("Settings import failed!", "ERROR!", + MessageBoxButton.OK, MessageBoxImage.Error); + } + } + } + + // + // Export settings + // + else if (sender == mainMenuExport) + { + SaveFileDialog dialog = new SaveFileDialog + { + InitialDirectory = Directory.GetCurrentDirectory(), + AddExtension = true, + DefaultExt = "xml", + Filter = "XML File|*.xml", + Title = "Export settings" + + }; + if (dialog.ShowDialog() == true) + { + try + { + UpdateSettingsToConfiguration(); + config.Write(dialog.FileName); + SetStatus("Settings exported!"); + } + catch (Exception) + { + MessageBox.Show("Settings export failed!", "ERROR!", + MessageBoxButton.OK, MessageBoxImage.Error); + } + } + + } + + // + // Reset to default + // + else if (sender == mainMenuResetToDefault) + { + WindowMessageBox messageBox = new WindowMessageBox( + "Are you sure?", "Reset to default settings?", + "Yes", "No"); + messageBox.ShowDialog(); + if (messageBox.DialogResult == true) + { + + config = null; + isFirstStart = true; + config = new Configuration(); + + // Initialize configuration + InitializeConfiguration(); + + // Restart driver + StopDriver(); + StartDriver(); + } + + } + + // + // Exit GUI only + // + else if (sender == mainMenuExitGUI) + { + driver.DoNotKill = true; + Close(); + } + + + + // + // Exit + // + else if (sender == mainMenuExit) + { + Close(); + } + + + + // + // Open current directory + // + else if (sender == mainMenuOpenCurrentDirectory) + { + try + { + Process.Start("explorer.exe", "."); + } + catch (Exception) + { + MessageBox.Show("Couldn't open TabletDriver folder!", "Error!", MessageBoxButton.OK, MessageBoxImage.Error); + } + } + + + // + // Open config folder + // + else if (sender == mainMenuOpenConfig) + { + try + { + Process.Start("explorer.exe", "config"); + } + catch (Exception) + { + MessageBox.Show("Couldn't open config folder!", "Error!", MessageBoxButton.OK, MessageBoxImage.Error); + } + } + + // + // Open tools folder + // + else if (sender == mainMenuOpenTools) + { + try + { + Process.Start("explorer.exe", "tools"); + } + catch (Exception) + { + MessageBox.Show("Couldn't open tools folder!", "Error!", MessageBoxButton.OK, MessageBoxImage.Error); + } + } + + + // + // Tablet View + // + else if (sender == mainMenuTabletView) + { + if (tabletView != null) + tabletView.Close(); + + tabletView = new WindowTabletView(config, driver) + { + WindowStartupLocation = WindowStartupLocation.CenterOwner, + Owner = Application.Current.MainWindow, + }; + tabletView.ShowDialog(); + tabletView.Close(); + tabletView = null; + GC.Collect(); + GC.WaitForPendingFinalizers(); + + } + + + // + // Tablet View Settings + // + else if (sender == mainMenuTabletViewSettings) + { + + WindowTabletViewSettings tabletViewSettings = new WindowTabletViewSettings(config) + { + WindowStartupLocation = WindowStartupLocation.CenterOwner, + Owner = Application.Current.MainWindow, + }; + tabletViewSettings.ShowDialog(); + } + + + // + // Update desktop image + // + else if (sender == mainMenuUpdateDesktopImage) + { + DispatcherTimer timer = new DispatcherTimer + { + Interval = new TimeSpan(0, 0, 0, 0, 200) + }; + timer.Tick += (s, ev) => + { + imageDesktopScreenshot.Source = null; + GC.Collect(); + GC.WaitForPendingFinalizers(); + UpdateDesktopImage(); + timer.Stop(); + }; + timer.Start(); + } + + + // + // Fit window to content + // + else if (sender == mainMenuFitToContent) + { + SizeToContent = SizeToContent.WidthAndHeight; + UpdateLayout(); + SizeToContent = SizeToContent.Manual; + } + + } + + + // + // Tab selection changed + // + private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + // + // Update canvas elements when changed to area tab + // + if (tabControl.SelectedItem == tabArea) + { + if (IsLoaded) + { + DispatcherTimer timer = new DispatcherTimer + { + Interval = new TimeSpan(0, 0, 0, 0, 100) + }; + timer.Tick += (snd, ev) => + { + UpdateCanvasElements(); + timer.Stop(); + }; + timer.Start(); + } + } + + // + // Console tab is selected + // + if (tabControl.SelectedItem == tabConsole) + { + ConsoleBufferToText(); + } + } + + } +} diff --git a/TabletDriverGUI/MainWindow.xaml b/TabletDriverGUI/MainWindow.xaml index 7e47861..f20350b 100644 --- a/TabletDriverGUI/MainWindow.xaml +++ b/TabletDriverGUI/MainWindow.xaml @@ -1,149 +1,342 @@  + Keyboard.PreviewKeyDown="Window_PreviewKeyDown" + Title="TabletDriverGUI" Height="750" Width="770"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + - - - + + + + + + + - + + + + + + + + + + + + + - - - - - + + + + + + + + + + + + + + + + + - + - + Screen Map - You can drag the screen area with a mouse. - - Drag + Control = Move area in X direction. + - Control + Drag = Move area in X direction. + + - Shift + Drag = Move area Y direction. + + - Alt + Drag = Move area 80 pixels at a time. - - Drag + Shift = Move area Y direction. + - Mouse scroll = Resize area 10 pixels per scroll. - - Use the "Set Area" to set the screen area to single monitor or full desktop. + - Control + Mouse scroll = Resize area 100 pixels per scroll. + + - Use the "Move Area To" to set move screen area to single monitor or full desktop. + + - Right-clicking the area will show more options - + + + + + + + + Grid.Row="0" + MinWidth="500" + MinHeight="130" + Margin="0,0,0,0" + Background="Transparent" + ClipToBounds="True" + Focusable="True" + ContextMenuOpening="CanvasArea_ContextMenuOpening" + MouseWheel="CanvasArea_MouseWheel" + MouseMove="CanvasArea_MouseMove" + MouseDown="CanvasArea_MouseDown" + MouseUp="CanvasArea_MouseUp"> + + + + + + + + + + + + + + - + + + + + + + + + + + + + + - - - - 1920 - - - + + + + + + + - - - - 1080 - - - + + + + + + + - - - - 0 - - - + + + + + + + - - - - 0 - - - + + + + - - - - + - + - - + - + Tablet Area - You can drag the tablet area with a mouse. - - Drag + Control = Move area in X direction. + - Control + Drag = Move area in X direction. + + - Shift + Drag = Move area Y direction. - - Drag + Shift = Move area Y direction. + - Alt + Drag = Move area 5 mm at a time. - - For left handed mode, use rotation value of 180 degrees. + - Mouse scroll = Resize area 1 mm per scroll. + + - Control + Mouse scroll = Resize area 10 mm per scroll. + + - For left handed mode, enable the "Left handed (Inverted)" checkbox or use a rotation value of 180 degrees. - Click Wacom Area button to type in the Wacom driver area settings. + + - Draw Area will allow you to set the tablet area by clicking two points with a pen. + + - Right-clicking the area will show more options. @@ -153,389 +346,1133 @@ - + - + + + + + + + + + Left handed + + (Invert) + + + + + + + 0 + + + - - - 0 - - - + - + + + + + + + + Width="260" + Height="150" + Margin="5,0,5,0" + Background="Transparent" + ClipToBounds="True" + Focusable="True" + ContextMenuOpening="CanvasArea_ContextMenuOpening" + MouseWheel="CanvasArea_MouseWheel" + MouseMove="CanvasArea_MouseMove" + MouseDown="CanvasArea_MouseDown" + MouseUp="CanvasArea_MouseUp"> + + + + + + + + + + + + + + - - - Full Area + + + + + + + Absolute (Pen) + Relative (Mouse) + + - - + + + + + Mode + + Standard: Gaming and other programs that don't need pen pressure. + + Windows Ink: Enables pen pressure for applications that support Windows Ink. + + Compatibility: Use this mode if you have problems with cursor movement in standard mode. + + - - - + + + + + - 80 - + + - 45 - + + - 0 - + + - 0 - + + - - - - Force - Aspect Ratio + + - - + + + + + Force aspect ratio + + + + + + - - + + + + + + + + - - + + + + + + + + + + + + + + + - - Button Mapping + + Pen Buttons - - You can disable a single button by selecting "Disable" from the list. + - You can disable a single button by clicking "Clear" in the mapping dialog. + + - The "Disable buttons" checkbox will disable all pen buttons. + + - Use negative number in scroll sensitivity to invert the direction. - - The "Disable buttons" checkbox will disable all tablet buttons. + - Scroll acceleration 1.0 = No acceleration. - + - - - - + + + + - - - + + - - - - + + + + Disable buttons + HorizontalAlignment="Left" + Margin="7,5,0,0" + Checked="CheckboxChanged" + Unchecked="CheckboxChanged"> + Disable buttons + + - - - - + + - - Cursor position smoothing filter + + Mouse Scroll - - Smoothing filter adds latency to the input, so don't enable it if you want to have the lowest possible input lag. - - - On Wacom tablets you can use latency value between 15 and 25 to have a similar smoothing as in the Wacom drivers. + - Use negative number in sensitivity to invert the direction. - - You can test out different filter values, but recommended maximum for osu! is around 50 milliseconds. + - Acceleration 1.0 = No acceleration. Recommended range is from 1 to 5. - - Filter latency value lower than 4 milliseconds isn't recommended. It is just better to disable the smoothing filter. + - Stop cursor: cursor will be stopped when scrolling. - - You don't have to change the filter rate, but you can use the highest rate your computer can run without performance problems. + - Drag scroll: scrolls only when the pen tip is pressed down. + + + + + + 0.5 + + + + + + + + 1.0 + + + + + + Stop cursor + + + + + Drag scroll + - - - + + + + + + + + + + - 0 - + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - 500 Hz - 333 Hz - 250 Hz - 125 Hz - + + + + + + + + + + + + + - - - Enabled - + + + + + + + + + + + - + + + + + + + + + + + + + + Auto Restart + + + + + Run at Windows startup + + - Run at Windows startup - + + + + + + - + 0.0.0 diff --git a/TabletDriverGUI/MainWindow.xaml.cs b/TabletDriverGUI/MainWindow.xaml.cs index 19e74b7..4328492 100644 --- a/TabletDriverGUI/MainWindow.xaml.cs +++ b/TabletDriverGUI/MainWindow.xaml.cs @@ -1,18 +1,16 @@ -using Microsoft.Win32; -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; -using System.Linq; -using System.Text; using System.Text.RegularExpressions; using System.Windows; using System.Windows.Controls; +using System.Windows.Ink; using System.Windows.Input; using System.Windows.Interop; using System.Windows.Media; -using System.Windows.Shapes; +using System.Windows.Media.Animation; using System.Windows.Threading; namespace TabletDriverGUI @@ -24,7 +22,7 @@ public partial class MainWindow : Window { // Version - public string Version = "0.1.1"; + public string Version = "0.2.4"; // Console stuff private List commandHistory; @@ -36,12 +34,16 @@ public partial class MainWindow : Window // Driver private TabletDriver driver; + private Dictionary driverCommands; + private List settingCommands; private bool running; + private int tabletButtonCount; // Timers private DispatcherTimer timerStatusbar; private DispatcherTimer timerRestart; private DispatcherTimer timerConsoleUpdate; + private DispatcherTimer timerUpdatePenPositions; // Config private Configuration config; @@ -49,34 +51,8 @@ public partial class MainWindow : Window private bool isFirstStart = false; private bool isLoadingSettings; - // Screen map canvas elements - private Rectangle[] rectangleMonitors; - private Rectangle rectangleDesktop; - private Rectangle rectangleScreenMap; - private TextBlock textScreenAspectRatio; - - // Tablet area canvas elements - private Polygon polygonTabletFullArea; - private Polygon polygonTabletArea; - private Polygon polygonTabletAreaArrow; - private TextBlock textTabletAspectRatio; - - // Mouse drag - private class MouseDrag - { - public bool IsMouseDown; - public object Source; - public Point OriginMouse; - public Point OriginDraggable; - public MouseDrag() - { - IsMouseDown = false; - Source = null; - OriginMouse = new Point(0, 0); - OriginDraggable = new Point(0, 0); - } - } - MouseDrag mouseDrag; + // Measurement to area + private bool isEnabledMeasurementToArea = false; // // Constructor @@ -86,6 +62,8 @@ public MainWindow() // Set the current directory as TabletDriverGUI.exe's directory. try { Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory); } catch (Exception) { } + // + // Prevent triggering input field events // isLoadingSettings = true; @@ -109,6 +87,7 @@ public MainWindow() ContextMenu = new System.Windows.Forms.ContextMenu(new System.Windows.Forms.MenuItem[] { new System.Windows.Forms.MenuItem("TabletDriverGUI " + Version), + new System.Windows.Forms.MenuItem("Restart Driver", NotifyRestartDriver), new System.Windows.Forms.MenuItem("Show", NotifyShowWindow), new System.Windows.Forms.MenuItem("Exit", NotifyExit) }) @@ -116,7 +95,7 @@ public MainWindow() notifyIcon.ContextMenu.MenuItems[0].Enabled = false; notifyIcon.Text = ""; - notifyIcon.DoubleClick += NotifyIcon_DoubleClick; + notifyIcon.MouseClick += NotifyIcon_Click; notifyIcon.Visible = true; IsRealExit = false; @@ -124,10 +103,15 @@ public MainWindow() commandHistory = new List { "" }; commandHistoryIndex = 0; + // Init setting commands list + settingCommands = new List(); + // Init tablet driver driver = new TabletDriver("TabletDriverService.exe"); + driverCommands = new Dictionary(); driver.MessageReceived += OnDriverMessageReceived; driver.ErrorReceived += OnDriverErrorReceived; + driver.StatusReceived += OnDriverStatusReceived; driver.Started += OnDriverStarted; driver.Stopped += OnDriverStopped; running = false; @@ -160,34 +144,24 @@ public MainWindow() // - // Buttom Map ComboBoxes + // Hide tablet button mapping // - comboBoxButton1.Items.Clear(); - comboBoxButton2.Items.Clear(); - comboBoxButton3.Items.Clear(); - comboBoxButton1.Items.Add("Disable"); - comboBoxButton2.Items.Add("Disable"); - comboBoxButton3.Items.Add("Disable"); - for (int i = 1; i <= 5; i++) - { - comboBoxButton1.Items.Add("Mouse " + i); - comboBoxButton2.Items.Add("Mouse " + i); - comboBoxButton3.Items.Add("Mouse " + i); - } - comboBoxButton1.SelectedIndex = 0; - comboBoxButton2.SelectedIndex = 0; - comboBoxButton3.SelectedIndex = 0; + groupBoxTabletButtons.Visibility = Visibility.Collapsed; - // - // Smoothing rate ComboBox - // - comboBoxSmoothingRate.Items.Clear(); - for (int i = 2; i <= 8; i++) + // Ink canvas undo history + inkCanvasUndoHistory = new StrokeCollection(); + + // Ink canvas drawing attributes + inkCanvasDrawingAttributes = new DrawingAttributes { - comboBoxSmoothingRate.Items.Add((1000.0 / i).ToString("0") + " Hz"); - } - comboBoxSmoothingRate.SelectedIndex = 2; + Width = 10, + Height = 10, + Color = Color.FromRgb(0x55, 0x55, 0x55), + StylusTip = StylusTip.Ellipse, + FitToCurve = false + }; + inkCanvas.DefaultDrawingAttributes = inkCanvasDrawingAttributes; // Process command line arguments ProcessCommandLineArguments(); @@ -197,32 +171,33 @@ public MainWindow() Loaded += MainWindow_Loaded; SizeChanged += MainWindow_SizeChanged; - // - isLoadingSettings = false; } - #region Window events // Window is closing -> Stop driver private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e) { + // Close tablet view + if (tabletView != null) + tabletView.Close(); + + // Hide notify icon notifyIcon.Visible = false; - try - { - config.Write(configFilename); - } - catch (Exception) - { - } - Stop(); + + // Write configuration to XML file + try { config.Write(configFilename); } + catch (Exception) { } + + // Stop driver + StopDriver(); } // Window loaded -> Start driver private void MainWindow_Loaded(object sender, RoutedEventArgs e) { - + // Configuration filename configFilename = "config/config.xml"; @@ -233,55 +208,120 @@ private void MainWindow_Loaded(object sender, RoutedEventArgs e) } catch (Exception) { - driver.ConsoleAddText("New config created!"); + driver.ConsoleAddLine("New config created!"); isFirstStart = true; config = new Configuration(); } - isLoadingSettings = true; - Width = config.WindowWidth; - Height = config.WindowHeight; + + // Create setting elements + CreateSettingElements(); + + // Create canvas elements + CreateCanvasElements(); + + // Initialize configuration + InitializeConfiguration(); + + + // Hide the window if the GUI is started as minimized + if (WindowState == WindowState.Minimized) + { + Hide(); + } + + // + // Allow input field events + // isLoadingSettings = false; + // Start the driver + StartDriver(); + + } + - if (!config.DeveloperMode) + // + // Window key down + // + private void Window_PreviewKeyDown(object sender, KeyEventArgs e) + { + + // Control + S -> Save settings + if (e.KeyboardDevice.Modifiers == ModifierKeys.Control && e.Key == Key.S) { + SaveSettings(sender, null); + e.Handled = true; } + // Control + R -> Restart driver + if (e.KeyboardDevice.Modifiers == ModifierKeys.Control && e.Key == Key.R) + { + RestartDriverClick(sender, null); + e.Handled = true; + } - // Invalid config -> Set defaults - if (config.ScreenArea.Width == 0 || config.ScreenArea.Height == 0) + + // Control + I -> Import settings + if (e.KeyboardDevice.Modifiers == ModifierKeys.Control && e.Key == Key.I) { - config.DesktopSize.Width = GetVirtualDesktopSize().Width; - config.DesktopSize.Height = GetVirtualDesktopSize().Height; - config.ScreenArea.Width = config.DesktopSize.Width; - config.ScreenArea.Height = config.DesktopSize.Height; - config.ScreenArea.X = 0; - config.ScreenArea.Y = 0; + MainMenuClick(mainMenuImport, null); + e.Handled = true; } - // Create canvas elements - CreateCanvasElements(); + // Control + E -> Export settings + if (e.KeyboardDevice.Modifiers == ModifierKeys.Control && e.Key == Key.E) + { + MainMenuClick(mainMenuExport, null); + e.Handled = true; + } - // Load settings from configuration - LoadSettingsFromConfiguration(); + // + // Move screen or tablet area with keys + // + if (canvasScreenMap.IsFocused || canvasTabletArea.IsFocused) + { + int deltaX = 0; + int deltaY = 0; - // Update the settings back to the configuration - UpdateSettingsToConfiguration(); + // Arrow keys to delta + if (e.Key == Key.Left) + deltaX = -1; + else if (e.Key == Key.Right) + deltaX = 1; + else if (e.Key == Key.Up) + deltaY = -1; + else if (e.Key == Key.Down) + deltaY = 1; + if (deltaX == 0 && deltaY == 0) + return; - // Console timer - timerConsoleUpdate.Start(); + // Control + arrow -> 10x movement + if (Keyboard.Modifiers.HasFlag(ModifierKeys.Control)) + { + deltaX *= 10; + deltaY *= 10; + } - // Set run at startup - SetRunAtStartup(config.RunAtStartup); + // Screen map + if (canvasScreenMap.IsFocused) + { + SetStatus("Sender: " + sender); + config.SelectedScreenArea.X += deltaX; + config.SelectedScreenArea.Y += deltaY; + LoadSettingsFromConfiguration(); + e.Handled = true; + } - // Hide the window if the GUI is started as minimized - if (WindowState == WindowState.Minimized) - { - Hide(); + // Tablet area + else if (canvasTabletArea.IsFocused) + { + config.SelectedTabletArea.X += deltaX; + config.SelectedTabletArea.Y += deltaY; + LoadSettingsFromConfiguration(); + e.Handled = true; + } } - // Start the driver - Start(); } @@ -315,13 +355,22 @@ void ProcessCommandLineArguments() #region Notify icon stuff - // Notify icon double click -> show window - private void NotifyIcon_DoubleClick(object sender, EventArgs e) + // + // Notify icon click -> show window + // + private void NotifyIcon_Click(object sender, System.Windows.Forms.MouseEventArgs e) { + // Is not mouse left button? + if (e.Button != System.Windows.Forms.MouseButtons.Left) + return; + + // Minimized -> Show window if (WindowState == WindowState.Minimized) { NotifyShowWindow(sender, e); } + + // Hide window else { NotifyHideWindow(sender, e); @@ -337,6 +386,13 @@ protected override void OnStateChanged(EventArgs e) } } + + // 'Restart driver' handler for taskbar menu + void NotifyRestartDriver(object sender, EventArgs e) + { + RestartDriverClick(sender, null); + } + // 'Hide' handler for taskbar menu void NotifyHideWindow(object sender, EventArgs e) { @@ -348,6 +404,7 @@ void NotifyShowWindow(object sender, EventArgs e) { Show(); WindowState = WindowState.Normal; + Activate(); } // 'Exit' handler for taskbar menu @@ -361,1767 +418,108 @@ void NotifyExit(object sender, EventArgs e) - #region Setting handlers + #region Statusbar // - // Load settings from configuration + // Update statusbar // - private void LoadSettingsFromConfiguration() + private void SetStatus(string text) { - isLoadingSettings = true; - - // - // Tablet area - // - textTabletAreaWidth.Text = Utils.GetNumberString(config.TabletArea.Width); - textTabletAreaHeight.Text = Utils.GetNumberString(config.TabletArea.Height); - textTabletAreaX.Text = Utils.GetNumberString(config.TabletArea.X); - textTabletAreaY.Text = Utils.GetNumberString(config.TabletArea.Y); - checkBoxForceAspect.IsChecked = config.ForceAspectRatio; - checkBoxForceFullArea.IsChecked = config.ForceFullArea; - switch (config.OutputMode) - { - case Configuration.OutputModes.Absolute: - radioModeAbsolute.IsChecked = true; - break; - case Configuration.OutputModes.Relative: - radioModeRelative.IsChecked = true; - break; - case Configuration.OutputModes.Digitizer: - radioModeDigitizer.IsChecked = true; - break; - } - textTabletAreaRotation.Text = Utils.GetNumberString(config.TabletArea.Rotation); - - - // - // Force full area - // - if (config.ForceFullArea) - { - textTabletAreaWidth.IsEnabled = false; - textTabletAreaHeight.IsEnabled = false; - textTabletAreaX.IsEnabled = false; - textTabletAreaY.IsEnabled = false; - } - else - { - textTabletAreaWidth.IsEnabled = true; - textTabletAreaHeight.IsEnabled = true; - textTabletAreaX.IsEnabled = true; - textTabletAreaY.IsEnabled = true; - } - - - // - // Screen area - // - textScreenAreaWidth.Text = Utils.GetNumberString(config.ScreenArea.Width, "0"); - textScreenAreaHeight.Text = Utils.GetNumberString(config.ScreenArea.Height, "0"); - textScreenAreaX.Text = Utils.GetNumberString(config.ScreenArea.X, "0"); - textScreenAreaY.Text = Utils.GetNumberString(config.ScreenArea.Y, "0"); - - - // - // Desktop size - // - if (config.AutomaticDesktopSize) - { - textDesktopWidth.Text = Utils.GetNumberString(GetVirtualDesktopSize().Width); - textDesktopHeight.Text = Utils.GetNumberString(GetVirtualDesktopSize().Height); - config.DesktopSize.Width = GetVirtualDesktopSize().Width; - config.DesktopSize.Height = GetVirtualDesktopSize().Height; - textDesktopWidth.IsEnabled = false; - textDesktopHeight.IsEnabled = false; - } - else - { - textDesktopWidth.Text = Utils.GetNumberString(config.DesktopSize.Width); - textDesktopHeight.Text = Utils.GetNumberString(config.DesktopSize.Height); - } - checkBoxAutomaticDesktopSize.IsChecked = config.AutomaticDesktopSize; - - - // Force aspect ratio - if (config.ForceAspectRatio) - { - config.TabletArea.Height = config.TabletArea.Width / (config.ScreenArea.Width / config.ScreenArea.Height); - textTabletAreaHeight.Text = Utils.GetNumberString(config.TabletArea.Height); - textTabletAreaHeight.IsEnabled = false; - } - - - // - // Move tablet area to a valid position - // - config.TabletArea.MoveInside(config.TabletFullArea); - textTabletAreaX.Text = Utils.GetNumberString(config.TabletArea.X); - textTabletAreaY.Text = Utils.GetNumberString(config.TabletArea.Y); - - - // - // Buttons - // - if (config.ButtonMap.Count() == 3) - { - comboBoxButton1.SelectedIndex = config.ButtonMap[0]; - comboBoxButton2.SelectedIndex = config.ButtonMap[1]; - comboBoxButton3.SelectedIndex = config.ButtonMap[2]; - } - else - { - config.ButtonMap = new int[] { 1, 2, 3 }; - } - checkBoxDisableButtons.IsChecked = config.DisableButtons; - - - // - // Smoothing filter - // - checkBoxSmoothing.IsChecked = config.SmoothingEnabled; - textSmoothingLatency.Text = Utils.GetNumberString(config.SmoothingLatency); - comboBoxSmoothingRate.SelectedIndex = config.SmoothingInterval - 2; - if (config.SmoothingEnabled) - { - textSmoothingLatency.IsEnabled = true; - comboBoxSmoothingRate.IsEnabled = true; - } - else - { - textSmoothingLatency.IsEnabled = false; - comboBoxSmoothingRate.IsEnabled = false; - } - - // - // Run at startup - // - checkRunAtStartup.IsChecked = config.RunAtStartup; - - - // - // Custom commands - // - string tmp = ""; - foreach (string command in config.CommandsBefore) - { - if (command.Trim().Length > 0) - tmp += command.Trim() + "\n"; - } - textCommandsBefore.Text = tmp; - - tmp = ""; - foreach (string command in config.CommandsAfter) + Application.Current.Dispatcher.Invoke(() => { - if (command.Trim().Length > 0) - tmp += command.Trim() + "\n"; - } - textCommandsAfter.Text = tmp; - - - // Update canvases - UpdateCanvasElements(); - - - isLoadingSettings = false; + textStatus.Text = text; + }); + timerStatusbar.Stop(); + timerStatusbar.Start(); } // - // Update settings to configuration + // Update statusbar // - private void UpdateSettingsToConfiguration() + private void SetStatusWarning(string text) { - if (isLoadingSettings) - return; - - bool oldValue; - - // Tablet area - if (Utils.ParseNumber(textTabletAreaWidth.Text, out double val)) - config.TabletArea.Width = val; - if (Utils.ParseNumber(textTabletAreaHeight.Text, out val)) - config.TabletArea.Height = val; - if (Utils.ParseNumber(textTabletAreaX.Text, out val)) - config.TabletArea.X = val; - if (Utils.ParseNumber(textTabletAreaY.Text, out val)) - config.TabletArea.Y = val; - if (Utils.ParseNumber(textTabletAreaRotation.Text, out val)) - config.TabletArea.Rotation = val; - - config.ForceAspectRatio = (bool)checkBoxForceAspect.IsChecked; - config.ForceFullArea = (bool)checkBoxForceFullArea.IsChecked; - - // Output Mode - if (radioModeAbsolute.IsChecked == true) config.OutputMode = Configuration.OutputModes.Absolute; - if (radioModeRelative.IsChecked == true) config.OutputMode = Configuration.OutputModes.Relative; - if (radioModeDigitizer.IsChecked == true) config.OutputMode = Configuration.OutputModes.Digitizer; - - - // Force full area - if (config.ForceFullArea) - { - // Set tablet area size to full area - config.TabletArea.Width = config.TabletFullArea.Width; - config.TabletArea.Height = config.TabletFullArea.Height; - - // Force aspect - if (config.ForceAspectRatio) - config.TabletArea.Height = config.TabletArea.Width / (config.ScreenArea.Width / config.ScreenArea.Height); - - // Fit area to full area - config.TabletArea.ScaleInside(config.TabletFullArea); - - textTabletAreaWidth.Text = Utils.GetNumberString(config.TabletArea.Width); - textTabletAreaHeight.Text = Utils.GetNumberString(config.TabletArea.Height); - - } - - // Force the tablet area to be inside of the full area - config.TabletArea.MoveInside(config.TabletFullArea); - - // Screen area - if (Utils.ParseNumber(textScreenAreaWidth.Text, out val)) - config.ScreenArea.Width = val; - if (Utils.ParseNumber(textScreenAreaHeight.Text, out val)) - config.ScreenArea.Height = val; - if (Utils.ParseNumber(textScreenAreaX.Text, out val)) - config.ScreenArea.X = val; - if (Utils.ParseNumber(textScreenAreaY.Text, out val)) - config.ScreenArea.Y = val; - - - // Desktop size - if (Utils.ParseNumber(textDesktopWidth.Text, out val)) - config.DesktopSize.Width = val; - if (Utils.ParseNumber(textDesktopHeight.Text, out val)) - config.DesktopSize.Height = val; - config.AutomaticDesktopSize = (bool)checkBoxAutomaticDesktopSize.IsChecked; - if (config.AutomaticDesktopSize == true) + Application.Current.Dispatcher.Invoke(() => { - textDesktopWidth.Text = Utils.GetNumberString(GetVirtualDesktopSize().Width); - textDesktopHeight.Text = Utils.GetNumberString(GetVirtualDesktopSize().Height); - config.DesktopSize.Width = GetVirtualDesktopSize().Width; - config.DesktopSize.Height = GetVirtualDesktopSize().Height; - } + textStatusWarning.Text = text; + }); + timerStatusbar.Stop(); + timerStatusbar.Start(); + } - // Force aspect ratio - if (config.ForceAspectRatio) + // + // Statusbar warning text click + // + private void StatusWarning_MouseDown(object sender, MouseButtonEventArgs e) + { + // Open Task Manager + if (textStatusWarning.Text.ToLower().Contains("priority")) { - config.TabletArea.Height = config.TabletArea.Width / (config.ScreenArea.Width / config.ScreenArea.Height); - textTabletAreaHeight.Text = Utils.GetNumberString(config.TabletArea.Height); + try { Process.Start("taskmgr.exe"); } catch (Exception) { } } + } - // Button map - config.ButtonMap[0] = comboBoxButton1.SelectedIndex; - config.ButtonMap[1] = comboBoxButton2.SelectedIndex; - config.ButtonMap[2] = comboBoxButton3.SelectedIndex; - config.DisableButtons = (bool)checkBoxDisableButtons.IsChecked; - - - - // Filter - config.SmoothingEnabled = (bool)checkBoxSmoothing.IsChecked; - config.SmoothingInterval = comboBoxSmoothingRate.SelectedIndex + 2; - if (Utils.ParseNumber(textSmoothingLatency.Text, out val)) - config.SmoothingLatency = val; - - if (config.SmoothingEnabled) - { - textSmoothingLatency.IsEnabled = true; - comboBoxSmoothingRate.IsEnabled = true; - } - else + // + // Statusbar timer tick + // + private void TimerStatusbar_Tick(object sender, EventArgs e) + { + Application.Current.Dispatcher.Invoke(() => { - textSmoothingLatency.IsEnabled = false; - comboBoxSmoothingRate.IsEnabled = false; - } - - // - // Run at startup - // - oldValue = config.RunAtStartup; - config.RunAtStartup = (bool)checkRunAtStartup.IsChecked; - if (config.RunAtStartup != oldValue) - SetRunAtStartup(config.RunAtStartup); - - - // Custom commands - List commandList = new List(); - foreach (string command in textCommandsBefore.Text.Split('\n')) - if (command.Trim().Length > 0) - commandList.Add(command.Trim()); - config.CommandsBefore = commandList.ToArray(); + textStatus.Text = ""; + textStatusWarning.Text = ""; + }); + timerStatusbar.Stop(); + } - commandList.Clear(); - foreach (string command in textCommandsAfter.Text.Split('\n')) - if (command.Trim().Length > 0) - commandList.Add(command.Trim()); - config.CommandsAfter = commandList.ToArray(); + #endregion - UpdateCanvasElements(); - } + #region WndProc // - // Set run at startup + // Add WndProc hook // - private void SetRunAtStartup(bool enabled) + protected override void OnSourceInitialized(EventArgs e) { - try - { - string path = System.Reflection.Assembly.GetExecutingAssembly().Location; - string entryName = "TabletDriverGUI"; - RegistryKey rk = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true); - if (enabled) - rk.SetValue(entryName, "\"" + path + "\" --hide"); - else - rk.DeleteValue(entryName, false); - - rk.Close(); - } - catch (Exception) - { - } + base.OnSourceInitialized(e); + HwndSource source = PresentationSource.FromVisual(this) as HwndSource; + source.AddHook(WndProc); } // - // Get desktop size + // Process Windows messages // - System.Drawing.Rectangle GetVirtualDesktopSize() + private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { - System.Drawing.Rectangle rect = new System.Drawing.Rectangle(); - // Windows 8 or greater needed for the multiscreen absolute mode - if (VersionHelper.IsWindows8OrGreater() || config.OutputMode == Configuration.OutputModes.Digitizer) + // Show TabletDriverGUI + if (msg == NativeMethods.WM_SHOWTABLETDRIVERGUI) { - rect.Width = System.Windows.Forms.SystemInformation.VirtualScreen.Width; - rect.Height = System.Windows.Forms.SystemInformation.VirtualScreen.Height; + if (WindowState == WindowState.Minimized) + { + NotifyShowWindow(null, null); + } + else + { + Activate(); + } } - else - { - rect.Width = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width; - rect.Height = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height; - } - return rect; + return IntPtr.Zero; } - // - // Get available screens - // - System.Windows.Forms.Screen[] GetAvailableScreens() - { - System.Windows.Forms.Screen[] screens; + #endregion - // Windows 8 or greater needed for the multiscreen absolute mode - if (VersionHelper.IsWindows8OrGreater() || config.OutputMode == Configuration.OutputModes.Digitizer) - screens = System.Windows.Forms.Screen.AllScreens; - else - screens = new System.Windows.Forms.Screen[] { System.Windows.Forms.Screen.PrimaryScreen }; - return screens; - } - - // - // Create canvas elements - // - void CreateCanvasElements() - { - // - // Screen map canvas - // - // Clear canvas - canvasScreenMap.Children.Clear(); - - - // Monitor rectangles - rectangleMonitors = new Rectangle[16]; - for (int i = 0; i < 16; i++) - { - rectangleMonitors[i] = new Rectangle - { - Width = 10, - Height = 10, - Stroke = Brushes.Black, - StrokeThickness = 1.0, - Fill = Brushes.Transparent, - Visibility = Visibility.Hidden - }; - canvasScreenMap.Children.Add(rectangleMonitors[i]); - } - - // - // Desktop area rectangle - // - rectangleDesktop = new Rectangle - { - Stroke = Brushes.Black, - StrokeThickness = 2.0, - Fill = Brushes.Transparent - }; - canvasScreenMap.Children.Add(rectangleDesktop); - - - // - // Screen map area rectangle - // - Brush brushScreenMap = new SolidColorBrush(Color.FromArgb(50, 20, 20, 20)); - rectangleScreenMap = new Rectangle - { - Stroke = Brushes.Black, - StrokeThickness = 2.0, - Fill = brushScreenMap - }; - canvasScreenMap.Children.Add(rectangleScreenMap); - - // - // Screen aspect ratio text - // - textScreenAspectRatio = new TextBlock - { - Text = "", - Foreground = Brushes.Black, - FontSize = 13, - FontWeight = FontWeights.Bold - }; - canvasScreenMap.Children.Add(textScreenAspectRatio); - - - - // - // Tablet area canvas - // - // - // Clear - canvasTabletArea.Children.Clear(); - - // - // Tablet full area polygon - // - polygonTabletFullArea = new Polygon - { - Stroke = new SolidColorBrush(Color.FromRgb(100, 100, 100)), - StrokeThickness = 2.0, - Points = new PointCollection - { - new Point(0,0), - new Point(0,0), - new Point(0,0), - new Point(0,0) - }, - }; - canvasTabletArea.Children.Add(polygonTabletFullArea); - - // - // Tablet area polygon - // - polygonTabletArea = new Polygon - { - Stroke = new SolidColorBrush(Color.FromRgb(0, 0, 0)), - Fill = new SolidColorBrush(Color.FromArgb(50, 20, 20, 20)), - StrokeThickness = 2.0, - Points = new PointCollection - { - new Point(0,0), - new Point(0,0), - new Point(0,0), - new Point(0,0) - }, - }; - canvasTabletArea.Children.Add(polygonTabletArea); - - - // - // Tablet area arrow polygon - // - polygonTabletAreaArrow = new Polygon - { - Fill = new SolidColorBrush(Color.FromArgb(50, 20, 20, 20)), - Points = new PointCollection - { - new Point(0,0), - new Point(0,0), - new Point(0,0) - }, - }; - canvasTabletArea.Children.Add(polygonTabletAreaArrow); - - - // - // Tablet area aspect ratio text - // - textTabletAspectRatio = new TextBlock - { - Text = "", - Foreground = Brushes.Black, - FontSize = 13, - FontWeight = FontWeights.Bold - }; - canvasTabletArea.Children.Add(textTabletAspectRatio); - - - - - // - // Canvas mouse drag - // - mouseDrag = new MouseDrag(); - } - - // - // Update canvas elements - // - void UpdateCanvasElements() - { - UpdateScreenMapCanvas(); - UpdateTabletAreaCanvas(); - } - - // - // Update screen map canvas elements - // - void UpdateScreenMapCanvas() - { - // Canvas element scaling - double scaleX = (canvasScreenMap.ActualWidth - 2) / config.DesktopSize.Width; - double scaleY = (canvasScreenMap.ActualHeight - 2) / config.DesktopSize.Height; - double scale = scaleX; - if (scaleX > scaleY) - scale = scaleY; - - // Centered offset - double offsetX = canvasScreenMap.ActualWidth / 2.0 - config.DesktopSize.Width * scale / 2.0; - double offsetY = canvasScreenMap.ActualHeight / 2.0 - config.DesktopSize.Height * scale / 2.0; - - - // Full desktop area - rectangleDesktop.Width = config.DesktopSize.Width * scale; - rectangleDesktop.Height = config.DesktopSize.Height * scale; - Canvas.SetLeft(rectangleDesktop, offsetX); - Canvas.SetTop(rectangleDesktop, offsetY); - - - // Screen map area - rectangleScreenMap.Width = config.ScreenArea.Width * scale; - rectangleScreenMap.Height = config.ScreenArea.Height * scale; - Canvas.SetLeft(rectangleScreenMap, offsetX + config.ScreenArea.X * scale); - Canvas.SetTop(rectangleScreenMap, offsetY + config.ScreenArea.Y * scale); - - // Screen aspect ratio text - textScreenAspectRatio.Text = Utils.GetNumberString(config.ScreenArea.Width / config.ScreenArea.Height, "0.###") + ":1"; - Canvas.SetLeft(textScreenAspectRatio, offsetX + - (config.ScreenArea.X + config.ScreenArea.Width / 2.0) * scale - - textScreenAspectRatio.ActualWidth / 2.0 - ); - Canvas.SetTop(textScreenAspectRatio, offsetY + - (config.ScreenArea.Y + config.ScreenArea.Height / 2.0) * scale - - textScreenAspectRatio.ActualHeight / 2.0 - ); - - - - - - // Screens - System.Windows.Forms.Screen[] screens = GetAvailableScreens(); - - // Monitor minimums - double minX = 99999; - double minY = 99999; - foreach (System.Windows.Forms.Screen screen in screens) - { - if (screen.Bounds.X < minX) minX = screen.Bounds.X; - if (screen.Bounds.Y < minY) minY = screen.Bounds.Y; - } - - - // Monitor rectangles - int rectangeIndex = 0; - foreach (System.Windows.Forms.Screen screen in screens) - { - double x = screen.Bounds.X - minX; - double y = screen.Bounds.Y - minY; - - rectangleMonitors[rectangeIndex].Visibility = Visibility.Visible; - rectangleMonitors[rectangeIndex].Width = screen.Bounds.Width * scale; - rectangleMonitors[rectangeIndex].Height = screen.Bounds.Height * scale; - Canvas.SetLeft(rectangleMonitors[rectangeIndex], offsetX + x * scale); - Canvas.SetTop(rectangleMonitors[rectangeIndex], offsetY + y * scale); - - rectangeIndex++; - if (rectangeIndex >= 16) break; - } - - } - - // - // Update tablet area canvas elements - // - void UpdateTabletAreaCanvas() - { - double fullWidth = config.TabletFullArea.Width; - double fullHeight = config.TabletFullArea.Height; - - // Canvas element scaling - double scaleX = (canvasTabletArea.ActualWidth - 2) / fullWidth; - double scaleY = (canvasTabletArea.ActualHeight - 2) / fullHeight; - double scale = scaleX; - if (scaleX > scaleY) - scale = scaleY; - - - double offsetX = canvasTabletArea.ActualWidth / 2.0 - fullWidth * scale / 2.0; - double offsetY = canvasTabletArea.ActualHeight / 2.0 - fullHeight * scale / 2.0; - - // - // Tablet full area - // - Point[] corners = config.TabletFullArea.Corners; - for (int i = 0; i < 4; i++) - { - Point p = corners[i]; - p.X *= scale; - p.Y *= scale; - p.X += config.TabletFullArea.X * scale + offsetX; - p.Y += config.TabletFullArea.Y * scale + offsetY; - polygonTabletFullArea.Points[i] = p; - } - - - // - // Tablet area - // - corners = config.TabletArea.Corners; - for (int i = 0; i < 4; i++) - { - Point p = corners[i]; - p.X *= scale; - p.Y *= scale; - p.X += config.TabletArea.X * scale + offsetX; - p.Y += config.TabletArea.Y * scale + offsetY; - polygonTabletArea.Points[i] = p; - } - - // - // Tablet area arrow - // - polygonTabletAreaArrow.Points[0] = new Point( - offsetX + config.TabletArea.X * scale, - offsetY + config.TabletArea.Y * scale - ); - - polygonTabletAreaArrow.Points[1] = new Point( - offsetX + corners[2].X * scale + config.TabletArea.X * scale, - offsetY + corners[2].Y * scale + config.TabletArea.Y * scale - ); - - polygonTabletAreaArrow.Points[2] = new Point( - offsetX + corners[3].X * scale + config.TabletArea.X * scale, - offsetY + corners[3].Y * scale + config.TabletArea.Y * scale - ); - - - // - // Tablet area aspect ratio text - // - textTabletAspectRatio.Text = Utils.GetNumberString(config.TabletArea.Width / config.TabletArea.Height, "0.###") + ":1"; - Canvas.SetLeft(textTabletAspectRatio, offsetX + (config.TabletArea.X) * scale - textTabletAspectRatio.ActualWidth / 2.0); - Canvas.SetTop(textTabletAspectRatio, offsetY + (config.TabletArea.Y) * scale - textTabletAspectRatio.ActualHeight / 2.0); - - - } - - - - - // - // Canvas mouse events - // - // - // Canvas mouse down - private void Canvas_MouseDown(object sender, MouseButtonEventArgs e) - { - - mouseDrag.IsMouseDown = true; - mouseDrag.Source = (UIElement)sender; - mouseDrag.OriginMouse = e.GetPosition((UIElement)mouseDrag.Source); - - // Screen Map - if (mouseDrag.Source == canvasScreenMap) - { - // Reset monitor selection - comboBoxMonitor.SelectedIndex = -1; - - mouseDrag.OriginDraggable = new Point(config.ScreenArea.X, config.ScreenArea.Y); - canvasScreenMap.CaptureMouse(); - } - - // Tablet Area - else if (mouseDrag.Source == canvasTabletArea) - { - mouseDrag.OriginDraggable = new Point(config.TabletArea.X, config.TabletArea.Y); - canvasTabletArea.CaptureMouse(); - } - } - - // Canvas mouse up - private void Canvas_MouseUp(object sender, MouseButtonEventArgs e) - { - mouseDrag.IsMouseDown = false; - LoadSettingsFromConfiguration(); - isLoadingSettings = true; - textScreenAreaX.Text = Utils.GetNumberString(config.ScreenArea.X, "0"); - textScreenAreaY.Text = Utils.GetNumberString(config.ScreenArea.Y, "0"); - textTabletAreaX.Text = Utils.GetNumberString(config.TabletArea.X); - textTabletAreaY.Text = Utils.GetNumberString(config.TabletArea.Y); - isLoadingSettings = false; - canvasScreenMap.ReleaseMouseCapture(); - canvasTabletArea.ReleaseMouseCapture(); - } - - // Canvas mouse move - private void Canvas_MouseMove(object sender, MouseEventArgs e) - { - Point position; - double dx, dy; - double scaleX = 0, scaleY = 0, scale = 0; - - // Canvas mouse drag - if (mouseDrag.IsMouseDown && mouseDrag.Source == sender) - { - position = e.GetPosition((UIElement)mouseDrag.Source); - - dx = position.X - mouseDrag.OriginMouse.X; - dy = position.Y - mouseDrag.OriginMouse.Y; - - if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) - dx = 0; - if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) - dy = 0; - - // Screen map canvas - if (mouseDrag.Source == canvasScreenMap) - { - scaleX = config.DesktopSize.Width / canvasScreenMap.ActualWidth; - scaleY = config.DesktopSize.Height / canvasScreenMap.ActualHeight; - scale = scaleY; - if (scaleX > scaleY) - scale = scaleX; - - config.ScreenArea.X = mouseDrag.OriginDraggable.X + dx * scale; - config.ScreenArea.Y = mouseDrag.OriginDraggable.Y + dy * scale; - UpdateScreenMapCanvas(); - } - - // Tablet area canvas - else if (mouseDrag.Source == canvasTabletArea) - { - scaleX = config.TabletFullArea.Width / canvasTabletArea.ActualWidth; - scaleY = config.TabletFullArea.Height / canvasTabletArea.ActualHeight; - scale = scaleY; - if (scaleX > scaleY) - scale = scaleX; - - config.TabletArea.X = mouseDrag.OriginDraggable.X + dx * scale; - config.TabletArea.Y = mouseDrag.OriginDraggable.Y + dy * scale; - - UpdateTabletAreaCanvas(); - } - - - } - } - - - // TextBox setting changed - private void TextChanged(object sender, TextChangedEventArgs e) - { - UpdateSettingsToConfiguration(); - } - - // Checkbox setting changed - private void CheckboxChanged(object sender, RoutedEventArgs e) - { - if (isLoadingSettings) return; - - - // Disable tablet area settings when full area is forced - if (checkBoxForceFullArea.IsChecked == true) - { - textTabletAreaWidth.IsEnabled = false; - textTabletAreaHeight.IsEnabled = false; - textTabletAreaX.IsEnabled = false; - textTabletAreaY.IsEnabled = false; - } - else - { - textTabletAreaWidth.IsEnabled = true; - textTabletAreaX.IsEnabled = true; - textTabletAreaY.IsEnabled = true; - - // Disable tablet area height when aspect ratio is forced - if (checkBoxForceAspect.IsChecked == true) - textTabletAreaHeight.IsEnabled = false; - else - textTabletAreaHeight.IsEnabled = true; - - - } - - // Disable button map selection when buttons are disabled - if (checkBoxDisableButtons.IsChecked == true) - { - comboBoxButton1.IsEnabled = false; - comboBoxButton2.IsEnabled = false; - comboBoxButton3.IsEnabled = false; - } - else - { - comboBoxButton1.IsEnabled = true; - comboBoxButton2.IsEnabled = true; - comboBoxButton3.IsEnabled = true; - } - - // Disable desktop size settings when automatic is checked - if (checkBoxAutomaticDesktopSize.IsChecked == true) - { - textDesktopWidth.Text = Utils.GetNumberString(GetVirtualDesktopSize().Width); - textDesktopHeight.Text = Utils.GetNumberString(GetVirtualDesktopSize().Height); - textDesktopWidth.IsEnabled = false; - textDesktopHeight.IsEnabled = false; - } - else - { - textDesktopWidth.IsEnabled = true; - textDesktopHeight.IsEnabled = true; - } - - UpdateSettingsToConfiguration(); - - if (sender == checkBoxForceFullArea) - { - LoadSettingsFromConfiguration(); - UpdateSettingsToConfiguration(); - } - } - - // Selection settings changed - private void ItemSelectionChanged(object sender, SelectionChangedEventArgs e) - { - UpdateSettingsToConfiguration(); - } - - // Window size changed - private void MainWindow_SizeChanged(object sender, SizeChangedEventArgs e) - { - if (!IsLoaded || isLoadingSettings) return; - if (WindowState != WindowState.Maximized) - { - config.WindowWidth = (int)e.NewSize.Width; - config.WindowHeight = (int)e.NewSize.Height; - } - } - - // Monitor combobox clicked -> create new monitor list - private void ComboBoxMonitor_MouseDown(object sender, MouseButtonEventArgs e) - { - comboBoxMonitor.Items.Clear(); - - - System.Windows.Forms.Screen[] screens = GetAvailableScreens(); - if (screens.Length > 1) - { - comboBoxMonitor.Items.Add("Full desktop"); - foreach (System.Windows.Forms.Screen screen in screens) - { - string name = screen.DeviceName; - if (screen.Primary) - name += " Main"; - - comboBoxMonitor.Items.Add(name); - } - } - else - { - comboBoxMonitor.Items.Add(System.Windows.Forms.Screen.PrimaryScreen.DeviceName); - } - - } - - // Monitor selected -> change screen map - private void ComboBoxMonitor_SelectionChanged(object sender, SelectionChangedEventArgs e) - { - System.Windows.Forms.Screen[] screens = GetAvailableScreens(); - double minX = 99999; - double minY = 99999; - foreach (System.Windows.Forms.Screen screen in screens) - { - if (screen.Bounds.X < minX) minX = screen.Bounds.X; - if (screen.Bounds.Y < minY) minY = screen.Bounds.Y; - } - - int index = comboBoxMonitor.SelectedIndex; - if (index == 0) - { - textScreenAreaX.Text = "0"; - textScreenAreaY.Text = "0"; - textScreenAreaWidth.Text = Utils.GetNumberString(config.DesktopSize.Width); - textScreenAreaHeight.Text = Utils.GetNumberString(config.DesktopSize.Height); - } - else if (index > 0) - { - index--; - if (index >= 0 && index < screens.Length) - { - textScreenAreaX.Text = Utils.GetNumberString(screens[index].Bounds.X - minX); - textScreenAreaY.Text = Utils.GetNumberString(screens[index].Bounds.Y - minY); - textScreenAreaWidth.Text = Utils.GetNumberString(screens[index].Bounds.Width); - textScreenAreaHeight.Text = Utils.GetNumberString(screens[index].Bounds.Height); - } - } - UpdateSettingsToConfiguration(); - } - - // - // Save settings - // - private void SaveSettings(object sender, RoutedEventArgs e) - { - try - { - config.Write(configFilename); - SendSettingsToDriver(); - SetStatus("Settings saved!"); - } - catch (Exception) - { - string dir = Directory.GetCurrentDirectory(); - MessageBox.Show("Error occured while saving the configuration.\n" + - "Make sure that it is possible to create and edit files in the '" + dir + "' directory.\n", - "ERROR!", MessageBoxButton.OK, MessageBoxImage.Error - ); - } - } - - // - // Apply settings - // - private void ApplySettings(object sender, RoutedEventArgs e) - { - SendSettingsToDriver(); - SetStatus("Settings applied!"); - } - - - // - // Main Menu Click - // - private void MainMenuClick(object sender, RoutedEventArgs e) - { - - // Import - if (sender == mainMenuImport) - { - OpenFileDialog dialog = new OpenFileDialog - { - InitialDirectory = Directory.GetCurrentDirectory(), - Filter = "XML File|*.xml" - }; - if (dialog.ShowDialog() == true) - { - try - { - Configuration tmpConfig = Configuration.CreateFromFile(dialog.FileName); - config = tmpConfig; - LoadSettingsFromConfiguration(); - SetStatus("Settings imported!"); - } - catch (Exception) - { - MessageBox.Show("Settings import failed!", "ERROR!", - MessageBoxButton.OK, MessageBoxImage.Error); - } - } - } - - // Export - else if (sender == mainMenuExport) - { - SaveFileDialog dialog = new SaveFileDialog - { - InitialDirectory = Directory.GetCurrentDirectory(), - AddExtension = true, - DefaultExt = "xml", - Filter = "XML File|*.xml" - - }; - if (dialog.ShowDialog() == true) - { - try - { - UpdateSettingsToConfiguration(); - config.Write(dialog.FileName); - SetStatus("Settings exported!"); - } - catch (Exception) - { - MessageBox.Show("Settings export failed!", "ERROR!", - MessageBoxButton.OK, MessageBoxImage.Error); - } - } - - } - - // Exit - else if (sender == mainMenuExit) - { - Close(); - } - - } - - #endregion - - - - #region Statusbar - - // - // Update statusbar - // - private void SetStatus(string text) - { - Application.Current.Dispatcher.Invoke(() => - { - textStatus.Text = text; - }); - timerStatusbar.Stop(); - timerStatusbar.Start(); - } - - // - // Update statusbar - // - private void SetStatusWarning(string text) - { - Application.Current.Dispatcher.Invoke(() => - { - textStatusWarning.Text = text; - }); - timerStatusbar.Stop(); - timerStatusbar.Start(); - } - - - // - // Statusbar warning text click - // - private void StatusWarning_MouseDown(object sender, MouseButtonEventArgs e) - { - // Open Task Manager - if (textStatusWarning.Text.ToLower().Contains("priority")) - { - try { Process.Start("taskmgr.exe"); } catch (Exception) { } - } - } - - - // - // Statusbar timer tick - // - private void TimerStatusbar_Tick(object sender, EventArgs e) - { - Application.Current.Dispatcher.Invoke(() => - { - textStatus.Text = ""; - textStatusWarning.Text = ""; - }); - timerStatusbar.Stop(); - } - - #endregion - - - - #region Tablet Driver - - // - // Driver event handlers - // - // - // Message - private void OnDriverMessageReceived(object sender, TabletDriver.DriverEventArgs e) - { - //ConsoleAddText(e.Message); - Application.Current.Dispatcher.Invoke(() => - { - ParseDriverStatus(e.Message); - }); - } - // Error - private void OnDriverErrorReceived(object sender, TabletDriver.DriverEventArgs e) - { - SetStatusWarning(e.Message); - - } - // Started - private void OnDriverStarted(object sender, EventArgs e) - { - driver.SendCommand("HIDList"); - driver.SendCommand("Echo"); - driver.SendCommand("Echo Driver version: " + Version); - try { driver.SendCommand("echo Windows version: " + Environment.OSVersion.VersionString); } catch (Exception) { } - try - { - driver.SendCommand("Echo Windows product: " + - Microsoft.Win32.Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "ProductName", "").ToString()); - driver.SendCommand("Echo Windows release: " + - Microsoft.Win32.Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "ReleaseId", "").ToString()); - } - catch (Exception) - { - } - driver.SendCommand("Echo"); - driver.SendCommand("CheckTablet"); - SendSettingsToDriver(); - driver.SendCommand("Info"); - driver.SendCommand("Start"); - driver.SendCommand("Log Off"); - driver.SendCommand("LogDirect False"); - driver.SendCommand("Echo"); - driver.SendCommand("Echo Driver started!"); - } - // Stopped - private void OnDriverStopped(object sender, EventArgs e) - { - if (running) - { - SetStatus("Driver stopped. Restarting! Check console !!!"); - driver.ConsoleAddText("Driver stopped. Restarting!"); - - // Run in the main application thread - Application.Current.Dispatcher.Invoke(() => - { - Title = "TabletDriverGUI"; - notifyIcon.Text = ""; - driver.Stop(); - timerRestart.Start(); - }); - - } - } - // Driver restart timer - private void TimerRestart_Tick(object sender, EventArgs e) - { - if (running) - { - driver.Start(config.DriverPath, config.DriverArguments); - } - timerRestart.Stop(); - } - - - - // - // Parse driver status messages - // - private void ParseDriverStatus(string line) - { - // Status line? - if (!line.Contains("[STATUS]")) return; - - // Parse status variable and value - Match match = Regex.Match(line, "^.+\\[STATUS\\] ([^ ]+) (.*?)$"); - if (!match.Success) return; - - string variableName = match.Groups[1].ToString().ToLower(); - string stringValue = match.Groups[2].ToString(); - - // - // Tablet Name - // - if (variableName == "tablet") - { - Title = "TabletDriverGUI - " + stringValue; - notifyIcon.Text = Title; - SetStatus("Connected to " + stringValue); - } - - // - // Tablet width - // - if (variableName == "width") - { - if (Utils.ParseNumber(stringValue, out double val)) - { - config.TabletFullArea.Width = val; - config.TabletFullArea.X = val / 2.0; - LoadSettingsFromConfiguration(); - UpdateSettingsToConfiguration(); - if (isFirstStart) - SendSettingsToDriver(); - } - } - - // - // Tablet height - // - if (variableName == "height") - { - if (Utils.ParseNumber(stringValue, out double val)) - { - config.TabletFullArea.Height = val; - config.TabletFullArea.Y = val / 2.0; - LoadSettingsFromConfiguration(); - UpdateSettingsToConfiguration(); - if (isFirstStart) - SendSettingsToDriver(); - - } - } - } - - // - // Send settings to the driver - // - private void SendSettingsToDriver() - { - if (!driver.IsRunning) return; - - // Commands before settings - if (config.CommandsBefore.Length > 0) - { - foreach (string command in config.CommandsBefore) - { - string tmp = command.Trim(); - if (tmp.Length > 0) - { - driver.SendCommand(tmp); - } - } - } - - - // Desktop size - driver.SendCommand("DesktopSize " + textDesktopWidth.Text + " " + textDesktopHeight.Text); - - - // Screen area - driver.SendCommand("ScreenArea " + - Utils.GetNumberString(config.ScreenArea.Width) + " " + Utils.GetNumberString(config.ScreenArea.Height) + " " + - Utils.GetNumberString(config.ScreenArea.X) + " " + Utils.GetNumberString(config.ScreenArea.Y) - ); - - // Tablet area - driver.SendCommand("Area " + - Utils.GetNumberString(config.TabletArea.Width) + " " + Utils.GetNumberString(config.TabletArea.Height) + " " + - Utils.GetNumberString(config.TabletArea.X) + " " + Utils.GetNumberString(config.TabletArea.Y) - ); - - // Rotation - driver.SendCommand("Rotate " + Utils.GetNumberString(config.TabletArea.Rotation)); - - // Output Mode - switch (config.OutputMode) - { - case Configuration.OutputModes.Absolute: - driver.SendCommand("Mode Absolute"); - break; - case Configuration.OutputModes.Relative: - driver.SendCommand("Mode Relative"); - driver.SendCommand("Sensitivity " + Utils.GetNumberString(config.ScreenArea.Width / config.TabletArea.Width)); - break; - case Configuration.OutputModes.Digitizer: - driver.SendCommand("Mode Digitizer"); - break; - } - - - // Button map - if (config.DisableButtons) - { - driver.SendCommand("ButtonMap 0 0 0"); - } - else - { - driver.SendCommand("ButtonMap " + String.Join(" ", config.ButtonMap)); - } - - // Smoothing filter - if (config.SmoothingEnabled) - { - driver.SendCommand("Smoothing " + Utils.GetNumberString(config.SmoothingLatency)); - driver.SendCommand("SmoothingInterval " + Utils.GetNumberString(config.SmoothingInterval)); - } - else - { - driver.SendCommand("Smoothing 0"); - } - - // Commands after settings - if (config.CommandsAfter.Length > 0) - { - foreach (string command in config.CommandsAfter) - { - string tmp = command.Trim(); - if (tmp.Length > 0) - { - driver.SendCommand(tmp); - } - } - } - } - - // - // Start driver - // - void Start() - { - if (running) return; - - // Try to start the driver - try - { - running = true; - driver.Start(config.DriverPath, config.DriverArguments); - if (!driver.IsRunning) - { - SetStatus("Can't start the driver! Check the console!"); - driver.ConsoleAddText("ERROR! Can't start the driver!"); - } - } - - // Start failed - catch (Exception e) - { - SetStatus("Can't start the driver! Check the console!"); - driver.ConsoleAddText("ERROR! Can't start the driver!\n " + e.Message); - } - } - - - // - // Stop driver - // - void Stop() - { - if (!running) return; - running = false; - driver.Stop(); - timerConsoleUpdate.Stop(); - } - - #endregion - - - - #region Console stuff - - // - // Console buffer to text - // - private void ConsoleBufferToText() - { - StringBuilder stringBuilder = new StringBuilder(); - - // Lock console - driver.ConsoleLock(); - - // Get console status - if (!driver.HasConsoleUpdated) - { - driver.ConsoleUnlock(); - return; - } - driver.HasConsoleUpdated = false; - - // Create a string from buffer - foreach (string line in driver.ConsoleBuffer) - { - stringBuilder.Append(line); - stringBuilder.Append("\r\n"); - } - - // Unlock console - driver.ConsoleUnlock(); - - // Set output - textConsole.Text = stringBuilder.ToString(); - - // Scroll to end - scrollConsole.ScrollToEnd(); - - } - - // Console update timer tick - private void TimerConsoleUpdate_Tick(object sender, EventArgs e) - { - ConsoleBufferToText(); - } - - - // - // Send a command to driver - // - private void ConsoleSendCommand(string line) - { - if (commandHistory.Last() != line) - { - commandHistory.Add(line); - } - commandHistoryIndex = commandHistory.Count(); - textConsoleInput.Text = ""; - textConsoleInput.ScrollToEnd(); - try - { - driver.SendCommand(line); - } - catch (Exception e) - { - driver.ConsoleAddText("Error! " + e.Message); - } - } - - // - // Console input key down - // - private void TextConsoleInput_KeyDown(object sender, KeyEventArgs e) - { - if (e.Key == Key.Enter) - { - string line = textConsoleInput.Text; - ConsoleSendCommand(line); - } - } - - // - // Console input preview key down - // - private void TextConsoleInput_PreviewKeyDown(object sender, KeyEventArgs e) - { - if (e.Key == Key.Up) - { - commandHistoryIndex--; - if (commandHistoryIndex < 0) commandHistoryIndex = 0; - textConsoleInput.Text = commandHistory[commandHistoryIndex]; - textConsoleInput.CaretIndex = textConsoleInput.Text.Length; - } - if (e.Key == Key.Down) - { - commandHistoryIndex++; - if (commandHistoryIndex > commandHistory.Count() - 1) - { - commandHistoryIndex = commandHistory.Count(); - textConsoleInput.Text = ""; - } - else - { - textConsoleInput.Text = commandHistory[commandHistoryIndex]; - textConsoleInput.CaretIndex = textConsoleInput.Text.Length; - } - } - } - - - // - // Search rows - // - private List SearchRows(List rows, string search, int rowsBefore, int rowsAfter) - { - List buffer = new List(rowsBefore); - List output = new List(); - int rowCounter = 0; - - foreach (string row in rows) - { - if (row.Contains(search)) - { - if (buffer.Count > 0) - { - foreach (string bufferLine in buffer) - { - output.Add(bufferLine); - } - buffer.Clear(); - } - output.Add(row.Trim()); - rowCounter = rowsAfter; - } - else if (rowCounter > 0) - { - output.Add(row.Trim()); - rowCounter--; - } - else - { - buffer.Add(row); - if (buffer.Count > rowsBefore) - { - buffer.RemoveAt(0); - } - } - } - return output; - } - - - // - // Console output context menu - // - private void ConsoleMenuClick(object sender, RoutedEventArgs e) - { - - - // Copy all - if (sender == menuCopyAll) - { - Clipboard.SetText(textConsole.Text); - SetStatus("Console output copied to clipboard"); - } - - // Copy debug messages - else if (sender == menuCopyDebug) - { - string clipboard = ""; - List rows; - driver.ConsoleLock(); - rows = SearchRows(driver.ConsoleBuffer, "[DEBUG]", 0, 0); - driver.ConsoleUnlock(); - foreach (string row in rows) - clipboard += row + "\r\n"; - Clipboard.SetText(clipboard); - SetStatus("Debug message copied to clipboard"); - } - - // Copy error messages - else if (sender == menuCopyErrors) - { - string clipboard = ""; - List rows; - driver.ConsoleLock(); - rows = SearchRows(driver.ConsoleBuffer, "[ERROR]", 1, 1); - driver.ConsoleUnlock(); - foreach (string row in rows) - clipboard += row + "\r\n"; - Clipboard.SetText(clipboard); - SetStatus("Error message copied to clipboard"); - } - - // Start debug log - else if (sender == menuStartDebug) - { - string logFilename = "debug_" + DateTime.Now.ToString("yyyy-MM-dd_hh_mm_ss") + ".txt"; - ConsoleSendCommand("log " + logFilename); - ConsoleSendCommand("debug 1"); - } - - // Stop debug log - else if (sender == menuStopDebug) - { - ConsoleSendCommand("log off"); - ConsoleSendCommand("debug 0"); - } - - // Open latest debug log - else if (sender == menuOpenDebug) - { - try - { - var files = Directory.GetFiles(".", "debug_*.txt").OrderBy(a => File.GetCreationTime(a)); - if (files.Count() > 0) - { - string file = files.Last().ToString(); - Process.Start(file); - } - } - catch (Exception) - { - } - } - - // Run benchmark - else if (sender == menuRunBenchmark) - { - ConsoleSendCommand("Benchmark"); - } - - // Copy Benchmark - else if (sender == menuCopyBenchmark) - { - string clipboard = ""; - List rows; - driver.ConsoleLock(); - rows = SearchRows(driver.ConsoleBuffer, " [STATUS] BENCHMARK ", 0, 0); - driver.ConsoleUnlock(); - foreach (string row in rows) - { - Match m = Regex.Match(row, " BENCHMARK ([0-9\\.]+) ([0-9\\.]+) ([0-9\\.]+) (.*)$"); - if (m.Success) - { - string tabletName = m.Groups[4].ToString(); - string totalPackets = m.Groups[1].ToString(); - string noiseWidth = m.Groups[2].ToString(); - string noiseHeight = m.Groups[3].ToString(); - clipboard = - "Tablet(" + tabletName + ") " + - "Noise(" + noiseWidth + " mm x " + noiseHeight + " mm) " + - "Packets(" + totalPackets + ")\r\n"; - } - } - - if (clipboard.Length > 0) - { - Clipboard.SetText(clipboard); - SetStatus("Benchmark result copied to clipboard"); - } - } - - // Open startup log - else if (sender == menuOpenStartup) - { - if (File.Exists("startuplog.txt")) - { - try { Process.Start("startuplog.txt"); } catch (Exception) { } - } - else - { - MessageBox.Show( - "Startup log not found!\n" + - "Make sure that it is possible to create and edit files in the '" + Directory.GetCurrentDirectory() + "' directory.\n", - "Error!", MessageBoxButton.OK, MessageBoxImage.Error - ); - } - } - - // Open driver folder - else if (sender == menuOpenFolder) - { - try { Process.Start("."); } catch (Exception) { } - } - - // Open GitHub page - else if (sender == menuOpenGithub) - { - try { Process.Start("https://github.com/hawku/TabletDriver"); } catch (Exception) { } - } - - // Open Latest URL - else if (sender == menuOpenLatestURL) - { - Regex regex = new Regex("(http[s]?://.+?)($|\\s)", RegexOptions.IgnoreCase | RegexOptions.Multiline); - MatchCollection matches = regex.Matches(textConsole.Text); - if (matches.Count > 0) - { - string url = matches[matches.Count - 1].Groups[0].ToString().Trim(); - try { Process.Start(url); } catch (Exception) { } - } - } - - // Report a problem - else if (sender == menuReportProblem) - { - try { Process.Start("https://github.com/hawku/TabletDriver/wiki/FAQ"); } catch (Exception) { } - } - - - } - - - - #endregion - - - - #region Wacom - - // - // Wacom Area - // - private void ButtonWacomArea_Click(object sender, RoutedEventArgs e) - { - WacomArea wacom = new WacomArea(); - wacom.textWacomLeft.Text = Utils.GetNumberString((config.TabletArea.X - config.TabletArea.Width / 2) * 100.0, "0"); - wacom.textWacomRight.Text = Utils.GetNumberString((config.TabletArea.X + config.TabletArea.Width / 2) * 100.0, "0"); - - wacom.textWacomTop.Text = Utils.GetNumberString((config.TabletArea.Y - config.TabletArea.Height / 2) * 100.0, "0"); - wacom.textWacomBottom.Text = Utils.GetNumberString((config.TabletArea.Y + config.TabletArea.Height / 2) * 100.0, "0"); - - wacom.ShowDialog(); - - // Set button clicked - if (wacom.DialogResult == true) - { - if ( - Utils.ParseNumber(wacom.textWacomLeft.Text, out double left) && - Utils.ParseNumber(wacom.textWacomRight.Text, out double right) && - Utils.ParseNumber(wacom.textWacomTop.Text, out double top) && - Utils.ParseNumber(wacom.textWacomBottom.Text, out double bottom) - ) - { - double width, height; - width = right - left; - height = bottom - top; - config.ForceAspectRatio = false; - config.ForceFullArea = false; - config.TabletArea.X = (left + width / 2.0) / 100.0; - config.TabletArea.Y = (top + height / 2.0) / 100.0; - config.TabletArea.Width = width / 100.0; - config.TabletArea.Height = height / 100.0; - LoadSettingsFromConfiguration(); - } - else - { - MessageBox.Show("Invalid values!", "Wacom area error!", MessageBoxButton.OK, MessageBoxImage.Error); - } - } - - wacom.Close(); - } - - #endregion - - - - #region WndProc - - // - // Add WndProc hook - // - protected override void OnSourceInitialized(EventArgs e) - { - base.OnSourceInitialized(e); - HwndSource source = PresentationSource.FromVisual(this) as HwndSource; - source.AddHook(WndProc); - } - - // - // Process Windows messages - // - private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) + private void MouseTest(object sender, MouseButtonEventArgs e) { - - // Show TabletDriverGUI - if (msg == NativeMethods.WM_SHOWTABLETDRIVERGUI) - { - if (WindowState == WindowState.Minimized) - { - NotifyShowWindow(null, null); - } - else - { - Activate(); - } - } - - return IntPtr.Zero; + SetStatus("Event: " + e.RoutedEvent.ToString() + ", Mouse at " + ((UIElement)sender).ToString() + "! " + e.ChangedButton.ToString() + " " + e.ButtonState.ToString()); } - #endregion - - } } \ No newline at end of file diff --git a/TabletDriverGUI/MultimediaTimer.cs b/TabletDriverGUI/MultimediaTimer.cs new file mode 100644 index 0000000..a1af83b --- /dev/null +++ b/TabletDriverGUI/MultimediaTimer.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace TabletDriverGUI +{ + + public class MultimediaTimer : IDisposable + { + private bool disposed = false; + private int interval, resolution; + private UInt32 timerId; + + // Hold the timer callback to prevent garbage collection. + private readonly NativeMethods.MultimediaTimerCallback Callback; + + public MultimediaTimer() + { + Callback = new NativeMethods.MultimediaTimerCallback(TimerCallbackMethod); + Resolution = 5; + Interval = 10; + } + + ~MultimediaTimer() + { + Dispose(false); + } + + public int Interval + { + get + { + return interval; + } + set + { + CheckDisposed(); + + if (value < 0) + throw new ArgumentOutOfRangeException("value"); + + interval = value; + if (Resolution > Interval) + Resolution = value; + } + } + + // Note minimum resolution is 0, meaning highest possible resolution. + public int Resolution + { + get + { + return resolution; + } + set + { + CheckDisposed(); + + if (value < 0) + throw new ArgumentOutOfRangeException("value"); + + resolution = value; + } + } + + public bool IsRunning + { + get { return timerId != 0; } + } + + public void Start() + { + CheckDisposed(); + + if (IsRunning) + throw new InvalidOperationException("Timer is already running"); + + // Event type = 0, one off event + // Event type = 1, periodic event + UInt32 userCtx = 0; + timerId = NativeMethods.TimeSetEvent((uint)Interval, (uint)Resolution, Callback, ref userCtx, 1); + if (timerId == 0) + { + int error = Marshal.GetLastWin32Error(); + throw new Win32Exception(error); + } + } + + public void Stop() + { + CheckDisposed(); + + if (!IsRunning) + throw new InvalidOperationException("Timer has not been started"); + + StopInternal(); + } + + private void StopInternal() + { + NativeMethods.TimeKillEvent(timerId); + timerId = 0; + } + + public event EventHandler Tick; + + public void Dispose() + { + Dispose(true); + } + + private void TimerCallbackMethod(uint id, uint msg, ref uint userCtx, uint rsv1, uint rsv2) + { + Tick?.Invoke(this, EventArgs.Empty); + } + + private void CheckDisposed() + { + if (disposed) + throw new ObjectDisposedException("MultimediaTimer"); + } + + private void Dispose(bool disposing) + { + if (disposed) + return; + + disposed = true; + if (IsRunning) + { + StopInternal(); + } + + if (disposing) + { + Tick = null; + GC.SuppressFinalize(this); + } + } + } +} diff --git a/TabletDriverGUI/NamedPipeClient.cs b/TabletDriverGUI/NamedPipeClient.cs new file mode 100644 index 0000000..f5ca15c --- /dev/null +++ b/TabletDriverGUI/NamedPipeClient.cs @@ -0,0 +1,245 @@ +using System; +using System.IO.Pipes; +using System.Text; +using System.Threading.Tasks; + +namespace TabletDriverGUI +{ + class NamedPipeClient + { + readonly string pipeName; + NamedPipeClientStream pipeStream; + + bool isRunning; + readonly object lockObject = new object(); + + public bool IsRunning + { + get + { + lock (lockObject) { return isRunning; } + } + set + { + lock (lockObject) { isRunning = value; } + } + } + + byte[] bufferRead; + + + public delegate void NamedPipeEventHandler(object sender, NamedPipeEventArgs e); + public event NamedPipeEventHandler MessageReceived; + public event EventHandler Connected; + public event EventHandler Disconnected; + public class NamedPipeEventArgs : EventArgs + { + public Message Message; + public NamedPipeEventArgs(Message message) + { + Message = message; + } + } + + public class Message + { + public byte[] Data; + public int Length; + public Message() + { + Length = 0; + } + } + + public class TaskResult + { + public bool IsSuccess { get; set; } + public string ErrorMessage { get; set; } + } + + + // + // Constructor + // + public NamedPipeClient(string pipeName) + { + this.pipeName = pipeName; + bufferRead = new byte[10240]; + } + + // + // Start + // + public bool Start() + { + if (!IsRunning) + { + IsRunning = true; + Console.WriteLine("new NamedPipeClientStream " + pipeName); + pipeStream = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous); + Console.WriteLine("Connecting to " + pipeName + "..."); + pipeStream.Connect(5000); + if (pipeStream.IsConnected) + { + Console.WriteLine("Connected to " + pipeName + "!"); + OnConnected(); + BeginRead(new Message()); + } + else + { + Console.WriteLine("Couldn't connect to " + pipeName + "!"); + return false; + } + return true; + } + return false; + } + + + // + // Stop + // + public bool Stop() + { + IsRunning = false; + + try + { + pipeStream.Close(); + pipeStream.Dispose(); + } + catch (Exception ex) + { + Console.WriteLine("Pipe stop error: " + ex.Message); + } + + OnDisconnected(); + + return true; + } + + + // + // Begin reading from the pipe + // + private void BeginRead(Message message) + { + try + { + //Console.WriteLine("BeginRead " + pipeName); + pipeStream.BeginRead(bufferRead, 0, bufferRead.Length, EndReadCallBack, message); + } + catch (Exception ex) + { + Console.WriteLine("BeginRead error: " + ex.Message); + } + } + + // + // End pipe read + // + private void EndReadCallBack(IAsyncResult result) + { + int bytesRead = pipeStream.EndRead(result); + //Console.WriteLine("EndRead length " + pipeName + ": " + bytesRead); + + if (bytesRead > 0) + { + var message = (Message)result.AsyncState; + + // Set message values + message.Length = bytesRead; + message.Data = bufferRead; + + // Process message + OnMessageReceived(message); + + // Start a new read + BeginRead(new Message()); + } + + // Client disconnected + else + { + if (IsRunning) + { + OnDisconnected(); + Stop(); + } + } + + } + + // + // End pipe write + // + private TaskResult EndWriteCallBack(IAsyncResult asyncResult) + { + pipeStream.EndWrite(asyncResult); + pipeStream.Flush(); + + return new TaskResult { IsSuccess = true }; + } + + + // + // Write message + // + public Task WriteMessage(string message) + { + var taskCompletionSource = new TaskCompletionSource(); + + if (pipeStream != null && pipeStream.IsConnected) + { + var buffer = Encoding.UTF8.GetBytes(message); + + //Console.WriteLine("Writing message to " + pipeName + ": " + message); + + try + { + pipeStream.BeginWrite(buffer, 0, buffer.Length, asyncResult => { + try + { + taskCompletionSource.SetResult(EndWriteCallBack(asyncResult)); + } + catch (Exception ex) + { + taskCompletionSource.SetException(ex); + } + }, null); + } + catch (Exception e) + { + Console.WriteLine("BeginWrite exception: " + e.Message); + } + } + + return taskCompletionSource.Task; + } + + // + // Pipe connected + // + void OnConnected() + { + Console.WriteLine(pipeName + " connected!"); + Connected?.Invoke(this, new EventArgs()); + } + + // + // Pipe disconnected + // + void OnDisconnected() + { + Disconnected?.Invoke(this, new EventArgs()); + } + + // + // Pipe message received + // + void OnMessageReceived(Message message) + { + MessageReceived?.Invoke(this, new NamedPipeEventArgs(message)); + } + } +} diff --git a/TabletDriverGUI/NativeMethods.cs b/TabletDriverGUI/NativeMethods.cs index f2e07dd..5a0ce3b 100644 --- a/TabletDriverGUI/NativeMethods.cs +++ b/TabletDriverGUI/NativeMethods.cs @@ -5,6 +5,46 @@ namespace TabletDriverGUI { public class NativeMethods { + [StructLayout(LayoutKind.Sequential)] + public struct RECT + { + public int Left, Top, Right, Bottom; + + public RECT(int left, int top, int right, int bottom) + { + Left = left; + Top = top; + Right = right; + Bottom = bottom; + } + + public RECT(System.Drawing.Rectangle r) : this(r.Left, r.Top, r.Right, r.Bottom) { } + + public int X + { + get { return Left; } + set { Right -= (Left - value); Left = value; } + } + + public int Y + { + get { return Top; } + set { Bottom -= (Top - value); Top = value; } + } + + public int Height + { + get { return Bottom - Top; } + set { Bottom = value + Top; } + } + + public int Width + { + get { return Right - Left; } + set { Right = value + Left; } + } + } + public const int HWND_BROADCAST = 0xffff; public static readonly int WM_SHOWTABLETDRIVERGUI = RegisterWindowMessage("WM_SHOWTABLETDRIVERGUI"); @@ -12,5 +52,26 @@ public class NativeMethods public static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam); [DllImport("user32")] public static extern int RegisterWindowMessage(string message); + + [DllImport("user32.dll", SetLastError = true)] + public static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect); + + + // + // Multimedia timer + // + internal delegate void MultimediaTimerCallback(UInt32 id, UInt32 msg, ref UInt32 userCtx, UInt32 rsv1, UInt32 rsv2); + + [DllImport("winmm.dll", SetLastError = true, EntryPoint = "timeSetEvent")] + internal static extern UInt32 TimeSetEvent(UInt32 msDelay, UInt32 msResolution, MultimediaTimerCallback callback, ref UInt32 userCtx, UInt32 eventType); + + [DllImport("winmm.dll", SetLastError = true, EntryPoint = "timeKillEvent")] + internal static extern void TimeKillEvent(UInt32 uTimerId); + + // + // GDI + // + [DllImport("gdi32.dll")] + public static extern bool DeleteObject(IntPtr hObject); } } diff --git a/TabletDriverGUI/TabletDriver.cs b/TabletDriverGUI/TabletDriver.cs index 11c0de7..8dc8562 100644 --- a/TabletDriverGUI/TabletDriver.cs +++ b/TabletDriverGUI/TabletDriver.cs @@ -1,8 +1,11 @@ using System; +using System.Text.RegularExpressions; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Timers; +using System.Text; +using System.Runtime.InteropServices; namespace TabletDriverGUI { @@ -12,36 +15,91 @@ public class TabletDriver public delegate void DriverEventHandler(object sender, DriverEventArgs e); public event DriverEventHandler MessageReceived; public event DriverEventHandler ErrorReceived; + public event DriverEventHandler StatusReceived; public event EventHandler Started; public event EventHandler Stopped; public enum DriverEventType { Error, - Message + Message, + Status } public class DriverEventArgs : EventArgs { public DriverEventType Type; public string Message; - public DriverEventArgs(DriverEventType type, string message) + public string Parameters; + public DriverEventArgs(DriverEventType type, string message, string parameters) { Type = type; Message = message; + Parameters = parameters; } } + // Console stuff public List ConsoleBuffer; public bool HasConsoleUpdated; - private int ConsoleMaxLines; + private readonly int ConsoleMaxLines; private System.Threading.Mutex mutexConsoleUpdate; + private Dictionary commands; + public Dictionary Commands { get { return commands; } } + + // Pipe stuff + NamedPipeClient pipeInput; + NamedPipeClient pipeOutput; + NamedPipeClient pipeState; + byte[] stateBytes; + StringBuilder messageBuilder; + + + [StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Unicode)] + [Serializable] + public struct TabletState + { + public int index; + + public int inputButtons; + public double inputX; + public double inputY; + public double inputPressure; + public double inputVelocity; + + public int outputButtons; + public double outputX; + public double outputY; + public double outputPressure; + } + public TabletState tabletState; + // Other variables - private string servicePath; + private readonly string servicePath; private Process processService; private Timer timerWatchdog; private bool running; - public bool IsRunning { get { return running; } } + private readonly object locker = new object(); + + public bool DoNotKill; + + public bool IsRunning + { + get + { + lock (locker) + { + return running; + } + } + set + { + lock (locker) + { + running = value; + } + } + } // // Constructor @@ -49,21 +107,48 @@ public DriverEventArgs(DriverEventType type, string message) public TabletDriver(string servicePath) { this.servicePath = servicePath; - this.processService = null; + processService = null; + DoNotKill = false; timerWatchdog = new Timer(2000); timerWatchdog.Elapsed += TimerWatchdog_Elapsed; ConsoleMaxLines = 300; ConsoleBuffer = new List(ConsoleMaxLines); mutexConsoleUpdate = new System.Threading.Mutex(); + + commands = new Dictionary(); + + messageBuilder = new StringBuilder(); + + pipeInput = new NamedPipeClient("TabletDriverOutput"); + pipeOutput = new NamedPipeClient("TabletDriverInput"); + pipeState = new NamedPipeClient("TabletDriverState"); + + stateBytes = new byte[Marshal.SizeOf(typeof(TabletState))]; + pipeInput.MessageReceived += PipeInput_MessageReceived; + pipeState.MessageReceived += PipeState_MessageReceived; + + pipeInput.Connected += (snd, e) => { pipeInput.WriteMessage("\n"); }; + pipeOutput.Connected += (snd, e) => + { + pipeOutput.WriteMessage("\n"); + }; + pipeState.Connected += (snd, e) => { pipeState.WriteMessage("\n"); }; + + + // Invoke driver started event + Started?.Invoke(this, new EventArgs()); + + } + // // Driver watchdog // private void TimerWatchdog_Elapsed(object sender, ElapsedEventArgs e) { - if (running) + if (IsRunning) { //Console.WriteLine("ID: " + processDriver.Id); if (processService.HasExited) @@ -73,14 +158,17 @@ private void TimerWatchdog_Elapsed(object sender, ElapsedEventArgs e) } processService.Refresh(); - switch(processService.PriorityClass) + if (processService != null && !processService.HasExited) { - case ProcessPriorityClass.High: - case ProcessPriorityClass.RealTime: - break; - default: - RaiseError("TabletDriverService priority too low! Run the GUI as an administrator or change the priority to high!"); - break; + switch (processService.PriorityClass) + { + case ProcessPriorityClass.High: + case ProcessPriorityClass.RealTime: + break; + default: + RaiseError("TabletDriverService priority too low! Run the GUI as an administrator or change the priority to high!"); + break; + } } } } @@ -88,26 +176,40 @@ private void TimerWatchdog_Elapsed(object sender, ElapsedEventArgs e) // // Send command to the driver service // - public void SendCommand(string line) + public void SendCommand(string command) { - if (running) - processService.StandardInput.WriteLine(line); + if (IsRunning) + { + + if (pipeOutput.IsRunning) + { + pipeOutput.WriteMessage(command); + } + else + { + processService.StandardInput.WriteLine(command); + } + } } // // Add text to console // - public void ConsoleAddText(string line) + public void ConsoleAddLine(string line) { - mutexConsoleUpdate.WaitOne(); - ConsoleBuffer.Add(line); - HasConsoleUpdated = true; + try + { + mutexConsoleUpdate.WaitOne(); + ConsoleBuffer.Add(line); + HasConsoleUpdated = true; - // Limit console buffer size - if (ConsoleBuffer.Count >= ConsoleMaxLines) - ConsoleBuffer.RemoveRange(0, ConsoleBuffer.Count - ConsoleMaxLines); + // Limit console buffer size + if (ConsoleBuffer.Count >= ConsoleMaxLines) + ConsoleBuffer.RemoveRange(0, ConsoleBuffer.Count - ConsoleMaxLines); + } + catch (Exception) { } - mutexConsoleUpdate.ReleaseMutex(); + try { mutexConsoleUpdate.ReleaseMutex(); } catch (Exception) { } } @@ -124,13 +226,136 @@ public void ConsoleUnlock() mutexConsoleUpdate.ReleaseMutex(); } + // + // Command name complete + // + public string CompleteCommandName(string inputText, bool showCommands) + { + List commandsFound = new List(); + string result = null; + + // Find commands + foreach (var item in commands) + { + if (item.Key.StartsWith(inputText.ToLower())) + { + commandsFound.Add(item.Value); + } + } + + + // Only one command found + if (commandsFound.Count == 1) + { + result = commandsFound[0]; + } + + // Multiple commands found + else if (commandsFound.Count > 1) + { + + // Sort + commandsFound.Sort(); + + // Create a string from found commands + string commandsString = " | "; + int maxWidth = 1; + foreach (string command in commandsFound) + { + if (command.Length > maxWidth) + { + maxWidth = command.Length; + } + } + int columns = (int)Math.Ceiling(100.0 / maxWidth); + int rows = (int)Math.Ceiling((double)commandsFound.Count / columns); + + string[,] commandMatrix = new string[rows, columns]; + int row = 0; + int column = 0; + + foreach (string command in commandsFound) + { + if (rows == 1) + { + commandMatrix[row, column] = command + " | "; + } + else + { + commandMatrix[row, column] = command.PadRight(maxWidth) + " | "; + } + row++; + if (row >= rows) + { + row = 0; + column++; + if (column >= columns) break; + } + } + + for (row = 0; row < rows; row++) + { + if (row != 0) + commandsString += "\r\n | "; + for (column = 0; column < columns; column++) + { + string commandString = commandMatrix[row, column]; + if (commandString != null) + { + commandsString += commandString; + } + } + } + + // Add commands to console output + if (showCommands) + { + ConsoleAddLine(""); + ConsoleAddLine("Commands: "); + ConsoleAddLine(commandsString); + } + + // Fill input text + string completedText = inputText; + + // Loop through commands + foreach (string name in commandsFound) + { + for (int i = 1; i <= name.Length; i++) + { + string completeTest = name.Substring(0, i); + bool completeOK = true; + + // Check if the command complete is OK + foreach (string name2 in commandsFound) + { + // Not OK + if (!name2.ToLower().StartsWith(completeTest.ToLower())) + { + completeOK = false; + break; + } + } + if (completeOK) + { + completedText = completeTest; + } + } + } + + result = completedText; + } + + return result; + } + // // Raise error message // private void RaiseError(string text) { - ErrorReceived?.Invoke(this, new DriverEventArgs(DriverEventType.Error, text)); + ErrorReceived?.Invoke(this, new DriverEventArgs(DriverEventType.Error, text, "")); } // @@ -154,27 +379,148 @@ private void ProcessService_Exited(object sender, EventArgs e) // private void ProcessService_ErrorDataReceived(object sender, DataReceivedEventArgs e) { - ConsoleAddText("ERROR! " + e.Data); - ErrorReceived?.Invoke(this, new DriverEventArgs(DriverEventType.Error, e.Data)); + ConsoleAddLine("ERROR! " + e.Data); + ErrorReceived?.Invoke(this, new DriverEventArgs(DriverEventType.Error, e.Data, "")); } // - // Driver service data received + // Driver service standard output data received // private void ProcessService_OutputDataReceived(object sender, DataReceivedEventArgs e) { if (e.Data == null) { - if (running) + if (IsRunning) Stop(); } else { - ConsoleAddText(e.Data); - MessageReceived?.Invoke(this, new DriverEventArgs(DriverEventType.Message, e.Data)); + //ProcessDriverMessage(e.Data + "\n"); + } + } + + + // + // Received input pipe message + // + private void PipeInput_MessageReceived(object sender, NamedPipeClient.NamedPipeEventArgs e) + { + + string stringMessage = Encoding.UTF8.GetString(e.Message.Data, 0, e.Message.Length); + //ConsoleAddLine("Pipe: '" + stringMessage + "'"); + ProcessDriverMessage(stringMessage); + + } + + // + // Received state pipe message + // + private void PipeState_MessageReceived(object sender, NamedPipeClient.NamedPipeEventArgs e) + { + GCHandle gcHandle; + TabletState readState; + + // Convert bytes to TabletState + if (e.Message.Length == stateBytes.Length) + { + gcHandle = GCHandle.Alloc(e.Message.Data, GCHandleType.Pinned); + readState = (TabletState)Marshal.PtrToStructure(gcHandle.AddrOfPinnedObject(), typeof(TabletState)); + gcHandle.Free(); + tabletState = readState; + } + } + + + // + // Process driver message + // + private void ProcessDriverMessage(string messageData) + { + //ConsoleAddLine("Message data: '" + messageData + "'"); + + // Add message data to stringbuilder + messageBuilder.Append(messageData); + + + // Find a line + string line = ""; + int index; + int startIndex = 0; + for (index = 0; index < messageBuilder.Length; index++) + { + char c = messageBuilder[index]; + if (c == '\n') + { + //ConsoleAddLine("New line at " + index); + if (index > 0 && index > startIndex + 1) + { + line = messageBuilder.ToString(startIndex, index - startIndex + 1).Trim(); + ProcessDriverMessageLine(line); + startIndex = index; + } + } + } + + // Remove lines from stringbuilder + if (startIndex < messageBuilder.Length) + { + messageBuilder.Remove(0, startIndex + 1); + } + else + { + messageBuilder.Clear(); + } + + + } + + // + // Process driver message line + // + private void ProcessDriverMessageLine(string line) + { + //ConsoleAddLine("Message line: '" + line + "'"); + + // Status line? + if (line.Contains("[STATUS]")) + { + + // Parse status variable and value + Match match = Regex.Match(line, "^.+\\[STATUS\\] ([^ ]+) (.*?)$"); + if (match.Success) + { + string variableName = match.Groups[1].ToString().ToLower(); + string parameters = match.Groups[2].ToString(); + + // + // Commands status message + // + if (variableName == "commands") + { + string[] commandNames = parameters.Split(' '); + string lowerCaseName; + foreach (string name in commandNames) + { + lowerCaseName = name.Trim().ToLower(); + if (lowerCaseName.Length > 0) + { + if (!commands.ContainsKey(lowerCaseName)) + { + commands.Add(lowerCaseName, name); + } + } + } + } + + StatusReceived?.Invoke(this, new DriverEventArgs(DriverEventType.Status, variableName, parameters)); + } } + + ConsoleAddLine(line); + MessageReceived?.Invoke(this, new DriverEventArgs(DriverEventType.Message, line, "")); } + // // Start the driver service // @@ -227,10 +573,14 @@ public void Start(string processPath, string arguments) { } - running = true; + IsRunning = true; timerWatchdog.Start(); - Started?.Invoke(this, new EventArgs()); + // Named pipes + pipeInput.Start(); + pipeOutput.Start(); + pipeState.Start(); + } // Start failed @@ -253,19 +603,36 @@ public void Start(string processPath, string arguments) // public void Stop() { - if (!running) return; - running = false; + if (!IsRunning) return; timerWatchdog.Stop(); + IsRunning = false; + + // Stop named pipe clients + pipeInput.Stop(); + pipeOutput.Stop(); + pipeState.Stop(); + + + // Kill service process + Console.WriteLine("Killing TabletDriverService"); try { - processService.CancelOutputRead(); - processService.Kill(); - processService.Dispose(); + if (!DoNotKill) + { + processService.CancelOutputRead(); + processService.Kill(); + processService.Dispose(); + } } - catch (Exception) + catch (Exception e) { + Debug.WriteLine("Service process error! " + e.Message); } + Stopped?.Invoke(this, new EventArgs()); + + System.Threading.Thread.Sleep(10); + } diff --git a/TabletDriverGUI/TabletDriverGUI.csproj b/TabletDriverGUI/TabletDriverGUI.csproj index ed30a36..43089b5 100644 --- a/TabletDriverGUI/TabletDriverGUI.csproj +++ b/TabletDriverGUI/TabletDriverGUI.csproj @@ -77,13 +77,43 @@ MSBuild:Compile Designer + + + + + WindowTabletViewSettings.xaml + + + WindowButtonMapping.xaml + + + + + - - WacomArea.xaml + + WindowMessageBox.xaml + + + WindowTabletView.xaml + + + WindowWacomArea.xaml + + + WindowAreaEditor.xaml + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + MSBuild:Compile Designer @@ -98,7 +128,19 @@ MainWindow.xaml Code - + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + Designer MSBuild:Compile diff --git a/TabletDriverGUI/Utils.cs b/TabletDriverGUI/Utils.cs index 1481209..1d03b79 100644 --- a/TabletDriverGUI/Utils.cs +++ b/TabletDriverGUI/Utils.cs @@ -1,4 +1,6 @@ using System.Globalization; +using System.IO; +using System.Runtime.Serialization.Formatters.Binary; namespace TabletDriverGUI { @@ -35,6 +37,11 @@ public static bool ParseNumber(string str, out double val) { CheckCultureInfo(); val = 0; + + // Replace commas with dots + str = str.Replace(',', '.'); + + // Parse if (double.TryParse(str, NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign, cultureInfo.NumberFormat, out double tmp)) { val = tmp; @@ -56,5 +63,25 @@ public static string GetNumberString(double val, string format) CheckCultureInfo(); return val.ToString(format, cultureInfo.NumberFormat); } + + + + + // + // RGB to hex string + // + public static string RGBToHex(byte r, byte g, byte b) + { + return string.Format("{0:X2}{1:X2}{2:X2}", r, g, b); ; + } + + // + // RGB to color hex string + // + public static string RGBToHexColor(byte r, byte g, byte b) + { + return "#" + RGBToHex(r, g, b); + } + } } diff --git a/TabletDriverGUI/WindowAreaEditor.xaml b/TabletDriverGUI/WindowAreaEditor.xaml new file mode 100644 index 0000000..f63366b --- /dev/null +++ b/TabletDriverGUI/WindowAreaEditor.xaml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + 123 + + + + + + + + 123 + + + + + + + + 123 + + + + + + + + 123 + + + + + + + + + + + + + 123 + + + + + + + + 123 + + + + + + + + 123 + + + + + + + + 123 + + + + + + + + + + + + diff --git a/TabletDriverGUI/WindowAreaEditor.xaml.cs b/TabletDriverGUI/WindowAreaEditor.xaml.cs new file mode 100644 index 0000000..39cf97c --- /dev/null +++ b/TabletDriverGUI/WindowAreaEditor.xaml.cs @@ -0,0 +1,156 @@ +using System.Windows; +using System.Windows.Controls; + +namespace TabletDriverGUI +{ + /// + /// Interaction logic for WindowAreaEditor.xaml + /// + public partial class WindowAreaEditor : Window + { + private Area screenArea, tabletArea; + private Configuration config; + private bool isLoadingSettings; + + public WindowAreaEditor(Configuration configuration, Area ScreenArea, Area TabletArea) + { + WindowStartupLocation = WindowStartupLocation.CenterOwner; + Owner = Application.Current.MainWindow; + + InitializeComponent(); + screenArea = ScreenArea; + tabletArea = TabletArea; + config = configuration; + isLoadingSettings = false; + LoadValues(); + } + + // + // Save click + // + private void ButtonSave_Click(object sender, RoutedEventArgs e) + { + StoreValues(); + DialogResult = true; + Close(); + } + + // + // Cancel click + // + private void ButtonCancel_Click(object sender, RoutedEventArgs e) + { + DialogResult = false; + Close(); + } + + + // + // Load values from configuration + // + private void LoadValues() + { + isLoadingSettings = true; + + // Screen area + textScreenAreaWidth.Text = Utils.GetNumberString(screenArea.Width); + textScreenAreaHeight.Text = Utils.GetNumberString(screenArea.Height); + textScreenAreaX.Text = Utils.GetNumberString(screenArea.X - screenArea.Width / 2.0); + textScreenAreaY.Text = Utils.GetNumberString(screenArea.Y - screenArea.Height / 2.0); + + // Tablet area + textTabletAreaWidth.Text = Utils.GetNumberString(tabletArea.Width); + textTabletAreaHeight.Text = Utils.GetNumberString(tabletArea.Height); + textTabletAreaX.Text = Utils.GetNumberString(tabletArea.X); + textTabletAreaY.Text = Utils.GetNumberString(tabletArea.Y); + + if(config.ForceAspectRatio) + { + textTabletAreaHeight.IsEnabled = false; + } else + { + textTabletAreaHeight.IsEnabled = true; + } + + + isLoadingSettings = false; + + + } + + // + // Store values + // + private void StoreValues() + { + + // Screen area + if (Utils.ParseNumber(textScreenAreaWidth.Text, out double value)) + screenArea.Width = value; + if (Utils.ParseNumber(textScreenAreaHeight.Text, out value)) + screenArea.Height = value; + if (Utils.ParseNumber(textScreenAreaX.Text, out value)) + screenArea.X = value + screenArea.Width / 2.0; + if (Utils.ParseNumber(textScreenAreaY.Text, out value)) + screenArea.Y = value + screenArea.Height / 2.0; + + // Tablet area + if (Utils.ParseNumber(textTabletAreaWidth.Text, out value)) + tabletArea.Width = value; + if (Utils.ParseNumber(textTabletAreaHeight.Text, out value)) + tabletArea.Height = value; + if (Utils.ParseNumber(textTabletAreaX.Text, out value)) + tabletArea.X = value; + if (Utils.ParseNumber(textTabletAreaY.Text, out value)) + tabletArea.Y = value; + + } + + // + // TextBox key up + // + private void OnTextBoxKeyUp(object sender, System.Windows.Input.KeyEventArgs e) + { + if(e.Key == System.Windows.Input.Key.Enter) + { + ButtonSave_Click(sender, null); + } + } + + // + // TextBox changed + // + private void OnTextBoxChanged(object sender, TextChangedEventArgs e) + { + if (!IsLoaded || isLoadingSettings) return; + + if (config.ForceAspectRatio) + { + double screenWidth, screenHeight; + double tabletWidth, tabletHeight; + double aspectRatio; + + screenWidth = screenArea.Width; + screenHeight = screenArea.Height; + tabletWidth = tabletArea.Width; + tabletHeight = tabletArea.Height; + + if (Utils.ParseNumber(textScreenAreaWidth.Text, out double value)) + screenWidth = value; + if (Utils.ParseNumber(textScreenAreaHeight.Text, out value)) + screenHeight = value; + + if (Utils.ParseNumber(textTabletAreaWidth.Text, out value)) + tabletWidth = value; + if (Utils.ParseNumber(textTabletAreaHeight.Text, out value)) + tabletHeight = value; + + aspectRatio = screenWidth / screenHeight; + tabletHeight = tabletWidth / aspectRatio; + textTabletAreaHeight.Text = Utils.GetNumberString(tabletHeight); + } + } + + + } +} diff --git a/TabletDriverGUI/WindowButtonMapping.xaml b/TabletDriverGUI/WindowButtonMapping.xaml new file mode 100644 index 0000000..e99202c --- /dev/null +++ b/TabletDriverGUI/WindowButtonMapping.xaml @@ -0,0 +1,70 @@ + + + + + + + + None + + + + CTRL+SHIFT+Z + + + + + None + + + + + CTRL+SHIFT+Z + + + + + + + + diff --git a/TabletDriverGUI/WindowButtonMapping.xaml.cs b/TabletDriverGUI/WindowButtonMapping.xaml.cs new file mode 100644 index 0000000..086785e --- /dev/null +++ b/TabletDriverGUI/WindowButtonMapping.xaml.cs @@ -0,0 +1,344 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Globalization; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; + +namespace TabletDriverGUI +{ + /// + /// Interaction logic for ButtonMapping.xaml + /// + public partial class WindowButtonMapping : Window + { + // WPF Button + Button button; + public string Result; + + public class ButtonBinding + { + public string Key; + public string Name; + public bool Visible; + + public ButtonBinding(string name) : this("", name) + { + } + public ButtonBinding(string key, string name) + { + Key = key; + Name = name; + Visible = true; + } + public override string ToString() + { + return Name; + } + } + + public OrderedDictionary mouseBindings; + public OrderedDictionary multimediaBindings; + + public WindowButtonMapping() + { + WindowStartupLocation = WindowStartupLocation.CenterOwner; + Owner = Application.Current.MainWindow; + + InitializeComponent(); + Result = ""; + + + mouseBindings = new OrderedDictionary() + { + { "", new ButtonBinding("None") }, + { "MOUSE1", new ButtonBinding("Mouse 1 (Left / Tip)") }, + { "MOUSE2", new ButtonBinding("Mouse 2 (Right / Barrel)") }, + { "MOUSE3", new ButtonBinding("Mouse 3 (Middle / Eraser)") }, + { "MOUSE4", new ButtonBinding("Mouse 4 (Back)") }, + { "MOUSE5", new ButtonBinding("Mouse 5 (Forward)") }, + { "MOUSESCROLLV", new ButtonBinding("Scroll Up/Down") }, + { "MOUSESCROLLH", new ButtonBinding("Scroll Left/Right") }, + { "MOUSESCROLLB", new ButtonBinding("Scroll Both") }, + { "SCROLLUP", new ButtonBinding("Scroll One Up") }, + { "SCROLLDOWN", new ButtonBinding("Scroll One Down") }, + { "SCROLLLEFT", new ButtonBinding("Scroll One Left") }, + { "SCROLLRIGHT", new ButtonBinding("Scroll One Right") }, + }; + + multimediaBindings = new OrderedDictionary() + { + { "", new ButtonBinding("None") }, + { "VOLUMEUP", new ButtonBinding("Volume Up") }, + { "VOLUMEUP0.5", new ButtonBinding("Volume Up 0.5%") }, + { "VOLUMEUP5.0", new ButtonBinding("Volume Up 5.0%") }, + { "VOLUMEDOWN", new ButtonBinding("Volume Down") }, + { "VOLUMEDOWN0.5", new ButtonBinding("Volume Down 0.5%") }, + { "VOLUMEDOWN5.0", new ButtonBinding("Volume Down 5.0%") }, + { "VOLUMEMUTE", new ButtonBinding("Volume Mute") }, + { "VOLUMECONTROL", new ButtonBinding("Volume Control Up/Down") }, + { "BALANCECONTROL", new ButtonBinding("Balance Control Left/Right") }, + { "MEDIANEXT", new ButtonBinding("Media Next Track") }, + { "MEDIAPREV", new ButtonBinding("Media Previous Track") }, + { "MEDIASTOP", new ButtonBinding("Media Stop") }, + { "MEDIAPLAY", new ButtonBinding("Media Play/Pause") }, + }; + + UpdateBindings(); + + } + + public WindowButtonMapping(Button button, bool isPenButton) : this() + { + this.button = button; + + // Tablet buttons don't need tip, barrel and eraser info + if (!isPenButton) + { + ((ButtonBinding)mouseBindings["MOUSE1"]).Name = "Mouse 1 (Left)"; + ((ButtonBinding)mouseBindings["MOUSE2"]).Name = "Mouse 2 (Right)"; + ((ButtonBinding)mouseBindings["MOUSE3"]).Name = "Mouse 3 (Middle)"; + } + UpdateBindings(); + + CheckKeyValue(); + } + + + // + // Update bindings and comboboxes + // + private void UpdateBindings() + { + + // Mouse + comboBoxMouse.Items.Clear(); + foreach (DictionaryEntry entry in mouseBindings) + { + ButtonBinding binding = (ButtonBinding)entry.Value; + binding.Key = (string)entry.Key; + if (binding.Visible) + { + comboBoxMouse.Items.Add(binding); + } + } + + // Multimedia + comboBoxMultimedia.Items.Clear(); + foreach (DictionaryEntry entry in multimediaBindings) + { + ButtonBinding binding = (ButtonBinding)entry.Value; + binding.Key = (string)entry.Key; + if (binding.Visible) + { + comboBoxMultimedia.Items.Add(binding); + } + } + + } + + + // + // Check key value + // + public void CheckKeyValue() + { + string keys = button.Content.ToString().ToUpper().Trim(); + comboBoxMouse.SelectedIndex = 0; + comboBoxMultimedia.SelectedIndex = 0; + + // Mouse + if (mouseBindings.Contains(keys)) + { + + foreach (var item in comboBoxMouse.Items) + { + ButtonBinding binding = (ButtonBinding)item; + if (binding.Key == keys) + { + comboBoxMouse.SelectedItem = item; + comboBoxMouse.Focus(); + } + } + } + + // Multimedia + else if (multimediaBindings.Contains(keys)) + { + comboBoxMultimedia.SelectedIndex = 0; + foreach (ButtonBinding item in comboBoxMultimedia.Items) + { + //ButtonBinding binding = (ButtonBinding)item; + if (item.Key == keys) + { + comboBoxMultimedia.SelectedItem = item; + comboBoxMultimedia.Focus(); + } + } + } + + // Keyboard + else + { + textKeyboard.Focus(); + } + + textKeyboard.Text = keys; + textCustom.Text = keys; + } + + + // + // Mouse button changed + // + private void ComboBoxMouse_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (!IsLoaded) return; + if (comboBoxMouse.SelectedIndex >= 0 && comboBoxMouse.SelectedItem != null) + { + ButtonBinding binding = (ButtonBinding)comboBoxMouse.SelectedItem; + if (comboBoxMouse.SelectedIndex > 0) + comboBoxMultimedia.SelectedIndex = 0; + textKeyboard.Text = binding.Key; + textCustom.Text = binding.Key; + } + } + + // + // Multimedia key changed + // + private void ComboBoxMultimedia_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (!IsLoaded) return; + if (comboBoxMultimedia.SelectedIndex >= 0 && comboBoxMultimedia.SelectedItem is ButtonBinding) + { + ButtonBinding binding = (ButtonBinding)comboBoxMultimedia.SelectedItem; + if (comboBoxMultimedia.SelectedIndex > 0) + comboBoxMouse.SelectedIndex = 0; + textKeyboard.Text = binding.Key; + textCustom.Text = binding.Key; + } + } + + + // + // Keyboard mapping key down + // + private void TextKeyboard_PreviewKeyDown(object sender, KeyEventArgs e) + { + System.Windows.Forms.KeysConverter keysConverter = new System.Windows.Forms.KeysConverter(); + CultureInfo cultureInfo = new CultureInfo("en-US"); + int keyCode; + List keys = new List(); + HashSet keyAdded = new HashSet(); + foreach (Key value in Enum.GetValues(typeof(Key))) + { + Key key = (Key)value; + if (key > Key.None && Keyboard.IsKeyDown(key)) + { + keyCode = KeyInterop.VirtualKeyFromKey(key); + + + string keyName = keysConverter.ConvertToString(null, cultureInfo, keyCode).ToUpper(); + switch (key) + { + case Key.LeftAlt: keyName = "LALT"; break; + case Key.RightAlt: keyName = "RALT"; break; + case Key.LeftCtrl: keyName = "LCTRL"; break; + case Key.RightCtrl: keyName = "RCTRL"; break; + case Key.LeftShift: keyName = "LSHIFT"; break; + case Key.RightShift: keyName = "RSHIFT"; break; + default: break; + } + if (!keyAdded.Contains(keyName)) + { + keys.Add(keyName); + keyAdded.Add(keyName); + } + } + } + + + keys.Sort( + (a, b) => + { + int value = 0; + if (a.Contains("CTRL")) value--; + if (a.Contains("ALT")) value--; + if (a.Contains("SHIFT")) value--; + if (b.Contains("CTRL")) value++; + if (b.Contains("ALT")) value++; + if (b.Contains("SHIFT")) value++; + return value; + } + ); + + // Set textboxes + string keyText = string.Join("+", keys.ToArray()); + textKeyboard.Text = keyText; + textCustom.Text = keyText; + + comboBoxMouse.SelectedIndex = 0; + + if (e != null) + e.Handled = true; + } + + private void Button_Click(object sender, RoutedEventArgs e) + { + + } + + // + // Set + // + private void ButtonSet_Click(object sender, RoutedEventArgs e) + { + if (button != null) + { + Result = textCustom.Text; + DialogResult = true; + Close(); + } + } + + + // + // Clear + // + private void ButtonClear_Click(object sender, RoutedEventArgs e) + { + if (button != null) + { + Result = ""; + DialogResult = true; + Close(); + } + } + + // + // Cancel + // + private void ButtonCancel_Click(object sender, RoutedEventArgs e) + { + DialogResult = false; + Close(); + } + + + // + // Mouse combobox or custom keys enter press -> set + // + private void OnEnterKeyUp(object sender, KeyEventArgs e) + { + if (e.Key == Key.Enter) + { + ButtonSet_Click(sender, null); + } + } + + + } +} diff --git a/TabletDriverGUI/WindowMessageBox.xaml b/TabletDriverGUI/WindowMessageBox.xaml new file mode 100644 index 0000000..f208c05 --- /dev/null +++ b/TabletDriverGUI/WindowMessageBox.xaml @@ -0,0 +1,39 @@ + + + + + + + + + + + diff --git a/TabletDriverGUI/WindowMessageBox.xaml.cs b/TabletDriverGUI/WindowMessageBox.xaml.cs new file mode 100644 index 0000000..3b4ac41 --- /dev/null +++ b/TabletDriverGUI/WindowMessageBox.xaml.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Shapes; + +namespace TabletDriverGUI +{ + /// + /// Interaction logic for WindowMessageBox.xaml + /// + public partial class WindowMessageBox : Window + { + public WindowMessageBox(string title, string message, string trueName, string falseName) + { + WindowStartupLocation = WindowStartupLocation.CenterOwner; + Owner = Application.Current.MainWindow; + + InitializeComponent(); + Title = title; + labelMessage.Content = message; + buttonTrue.Content = trueName; + buttonFalse.Content = falseName; + } + + private void ButtonTrue_Click(object sender, RoutedEventArgs e) + { + DialogResult = true; + Close(); + } + + private void ButtonFalse_Click(object sender, RoutedEventArgs e) + { + DialogResult = false; + Close(); + } + } +} diff --git a/TabletDriverGUI/WindowTabletView.xaml b/TabletDriverGUI/WindowTabletView.xaml new file mode 100644 index 0000000..10dd1bb --- /dev/null +++ b/TabletDriverGUI/WindowTabletView.xaml @@ -0,0 +1,62 @@ + + + + + + + Test Tablet - 100x55mm - 1920x1080 + + + + + Input + + + + + Output + + + + + ±100ms + + + diff --git a/TabletDriverGUI/WindowTabletView.xaml.cs b/TabletDriverGUI/WindowTabletView.xaml.cs new file mode 100644 index 0000000..d0413bf --- /dev/null +++ b/TabletDriverGUI/WindowTabletView.xaml.cs @@ -0,0 +1,664 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace TabletDriverGUI +{ + + /// + /// Interaction logic for WindowTabletView.xaml + /// + public partial class WindowTabletView : Window + { + Configuration config; + TabletDriver driver; + TabletRenderer tabletRenderer; + MultimediaTimer timer; + Vector lastPosition; + DateTime lastUpdate; + DateTime lastInputStartTime; + double lastPressure; + bool hadInputLoss; + double velocity; + double latency; + + + // + // TabletViewElement + // + public class TabletRenderer : UIElement + { + + public Point[] LastPositionsInput; + public Point[] LastPositionsOutput; + public Point[] DrawPositions; + public Point PositionInput; + public Point PositionOutput; + public double PressureInput; + public double PressureOutput; + public double ScaleX; + public double ScaleY; + + public readonly Brush brushInput; + public readonly Brush brushOutput; + public readonly Brush brushDraw; + + private readonly Pen penInputLine; + private readonly Pen penOutputLine; + private readonly Pen penDrawLine; + private readonly Pen penInputCircle; + private readonly Pen penOutputCircle; + private readonly Pen penInputPressure; + private readonly Pen penOutputPressure; + + public Rect rectInputPressure; + public Rect rectOutputPressure; + public readonly Rect rectInputPressureBorder; + public readonly Rect rectOutputPressureBorder; + + + // + // Tablet renderer constructor + // + public TabletRenderer(Configuration config) + { + // + // Last position arrays + // + LastPositionsInput = new Point[config.TabletView.InputTrailLength]; + LastPositionsOutput = new Point[config.TabletView.OutputTrailLength]; + for (int i = 0; i < LastPositionsInput.Length; i++) + LastPositionsInput[i] = new Point(-1, -1); + for (int i = 0; i < LastPositionsOutput.Length; i++) + LastPositionsOutput[i] = new Point(-1, -1); + + + // + // Draw position array + // + DrawPositions = new Point[config.TabletView.DrawLength]; + for (int i = 0; i < DrawPositions.Length; i++) + { + DrawPositions[i] = new Point(-1, -1); + } + + // Input/output positions + PositionInput = new Point(0, 0); + PositionOutput = new Point(0, 0); + + // Colors + Color colorInput = Colors.Green; + try { colorInput = (Color)ColorConverter.ConvertFromString(config.TabletView.InputColor); } catch (Exception) { } + Color colorOutput = Colors.Red; + try { colorOutput = (Color)ColorConverter.ConvertFromString(config.TabletView.OutputColor); } catch (Exception) { } + Color colorDraw = Colors.White; + try { colorDraw = (Color)ColorConverter.ConvertFromString(config.TabletView.DrawColor); } catch (Exception) { } + + // Brushes + brushInput = new SolidColorBrush(colorInput); + brushInput.Freeze(); + brushOutput = new SolidColorBrush(colorOutput); + brushOutput.Freeze(); + brushDraw = new SolidColorBrush(colorDraw); + brushDraw.Freeze(); + + // Line pens + penInputLine = new Pen(brushInput, 3) + { + StartLineCap = PenLineCap.Round, + EndLineCap = PenLineCap.Round, + LineJoin = PenLineJoin.Round + }; + penInputLine.Freeze(); + penOutputLine = new Pen(brushOutput, 3) + { + StartLineCap = PenLineCap.Round, + EndLineCap = PenLineCap.Round, + LineJoin = PenLineJoin.Round + }; + penOutputLine.Freeze(); + penDrawLine = new Pen(brushDraw, 3) + { + StartLineCap = PenLineCap.Round, + EndLineCap = PenLineCap.Round, + LineJoin = PenLineJoin.Round + }; + penDrawLine.Freeze(); + + // Circle pens + penInputCircle = new Pen(brushInput, 3); + penInputCircle.Freeze(); + penOutputCircle = new Pen(brushOutput, 3); + penOutputCircle.Freeze(); + + // Pressure border pens + penInputPressure = new Pen(brushInput, 3) + { + StartLineCap = PenLineCap.Round, + EndLineCap = PenLineCap.Round + }; + penInputPressure.Freeze(); + penOutputPressure = new Pen(brushOutput, 3) + { + StartLineCap = PenLineCap.Round, + EndLineCap = PenLineCap.Round + }; + penOutputPressure.Freeze(); + + // Rects + rectInputPressure = + new Rect(120, 60, 200, 20); + rectInputPressureBorder = + new Rect(120, 60, 200, 20); + rectOutputPressure = + new Rect(120, 90, 200, 20); + rectOutputPressureBorder = + new Rect(120, 90, 200, 20); + + // Offset pressure bars + rectInputPressure.X += config.TabletView.OffsetPressure.X; + rectInputPressure.Y += config.TabletView.OffsetPressure.Y; + rectInputPressureBorder.X += config.TabletView.OffsetPressure.X; + rectInputPressureBorder.Y += config.TabletView.OffsetPressure.Y; + rectOutputPressure.X += config.TabletView.OffsetPressure.X; + rectOutputPressure.Y += config.TabletView.OffsetPressure.Y; + rectOutputPressureBorder.X += config.TabletView.OffsetPressure.X; + rectOutputPressureBorder.Y += config.TabletView.OffsetPressure.Y; + + } + + + // + // Update last position arrays + // + public void UpdateLastPositions() + { + if (LastPositionsInput.Length != 0) + { + Array.Copy(LastPositionsInput, 1, LastPositionsInput, 0, LastPositionsInput.Length - 1); + LastPositionsInput[LastPositionsInput.Length - 1].X = PositionInput.X; + LastPositionsInput[LastPositionsInput.Length - 1].Y = PositionInput.Y; + } + if (LastPositionsOutput.Length != 0) + { + Array.Copy(LastPositionsOutput, 1, LastPositionsOutput, 0, LastPositionsOutput.Length - 1); + LastPositionsOutput[LastPositionsOutput.Length - 1].X = PositionOutput.X; + LastPositionsOutput[LastPositionsOutput.Length - 1].Y = PositionOutput.Y; + } + } + + + // + // Update draw position array + // + public void AddDrawPosition(double x, double y) + { + if (DrawPositions.Length == 0) return; + + // Shift position array + Array.Copy(DrawPositions, 1, DrawPositions, 0, DrawPositions.Length - 1); + + // Set currect position as last item in the array + DrawPositions[DrawPositions.Length - 1].X = x; + DrawPositions[DrawPositions.Length - 1].Y = y; + } + + + // + // Render + // + protected override void OnRender(DrawingContext context) + { + PathFigure path; + PathGeometry geometry; + + // Pressure bars + rectInputPressure.Width = PressureInput * rectInputPressureBorder.Width; + rectOutputPressure.Width = PressureOutput * rectInputPressureBorder.Width; + context.DrawRectangle(brushInput, null, rectInputPressure); + context.DrawRectangle(brushOutput, null, rectOutputPressure); + context.DrawRectangle(null, penInputPressure, rectInputPressureBorder); + context.DrawRectangle(null, penOutputPressure, rectOutputPressureBorder); + + // + // Drawing line + // + if (DrawPositions.Length > 0) + { + path = new PathFigure + { + StartPoint = DrawPositions[0] + }; + for (int i = 1; i < DrawPositions.Length; i++) + { + // Last empty + if (DrawPositions[i].X >= 0 && DrawPositions[i - 1].X <= 0) + { + path.Segments.Add(new LineSegment(DrawPositions[i], false)); + path.Segments.Add(new LineSegment(DrawPositions[i], true)); + } + // Current and last OK + else if (DrawPositions[i].X >= 0 && DrawPositions[i - 1].X >= 0) + path.Segments.Add(new LineSegment(DrawPositions[i], true)); + + // Current and last empty + else + path.Segments.Add(new LineSegment(DrawPositions[i], false)); + } + path.Freeze(); + geometry = new PathGeometry(); + geometry.Figures.Add(path); + geometry.Freeze(); + context.DrawGeometry(null, penDrawLine, geometry); + } + + // + // Input line + // + if (LastPositionsInput.Length > 0) + { + path = new PathFigure + { + StartPoint = LastPositionsInput[0] + }; + for (int i = 1; i < LastPositionsInput.Length; i++) + path.Segments.Add(new LineSegment(LastPositionsInput[i], true)); + path.Freeze(); + geometry = new PathGeometry(); + geometry.Figures.Add(path); + geometry.Freeze(); + context.DrawGeometry(null, penInputLine, geometry); + } + + + // + // Output line + // + if (LastPositionsOutput.Length > 0) + { + path = new PathFigure + { + IsClosed = false, + IsFilled = false, + StartPoint = LastPositionsOutput[0] + }; + for (int i = 1; i < LastPositionsOutput.Length; i++) + path.Segments.Add(new LineSegment(LastPositionsOutput[i], true)); + path.Freeze(); + geometry = new PathGeometry(); + geometry.Figures.Add(path); + geometry.Freeze(); + context.DrawGeometry(null, penOutputLine, geometry); + } + + // Input circle + context.DrawEllipse(null, penInputCircle, PositionInput, 12, 12); + + // Output circle + context.DrawEllipse(null, penOutputCircle, PositionOutput, 12, 12); + + } + + } + + // + // Tablet View Constructor + // + public WindowTabletView(Configuration config, TabletDriver driver) + { + if(config.TabletView.Borderless) + { + WindowStyle = WindowStyle.None; + } + InitializeComponent(); + + this.config = config; + this.driver = driver; + + // Tablet renderer + tabletRenderer = new TabletRenderer(config); + canvasTabletView.Children.Add(tabletRenderer); + + // Offset texts + Canvas.SetLeft(textTabletInfo, Canvas.GetLeft(textTabletInfo) + config.TabletView.OffsetText.X); + Canvas.SetTop(textTabletInfo, Canvas.GetTop(textTabletInfo) + config.TabletView.OffsetText.Y); + Canvas.SetLeft(textInput, Canvas.GetLeft(textInput) + config.TabletView.OffsetText.X); + Canvas.SetTop(textInput, Canvas.GetTop(textInput) + config.TabletView.OffsetText.Y); + Canvas.SetLeft(textOutput, Canvas.GetLeft(textOutput) + config.TabletView.OffsetText.X); + Canvas.SetTop(textOutput, Canvas.GetTop(textOutput) + config.TabletView.OffsetText.Y); + Canvas.SetLeft(textLatency, Canvas.GetLeft(textLatency) + config.TabletView.OffsetText.X); + Canvas.SetTop(textLatency, Canvas.GetTop(textLatency) + config.TabletView.OffsetText.Y); + + // Background color + Brush brush; + try { brush = new SolidColorBrush((Color)ColorConverter.ConvertFromString(config.TabletView.BackgroundColor)); } + catch (Exception) { brush = Brushes.White; } + canvasTabletView.Background = brush; + Background = brush; + + // Text colors + try { brush = new SolidColorBrush((Color)ColorConverter.ConvertFromString(config.TabletView.InfoColor)); } + catch (Exception) { brush = Brushes.Black; } + textTabletInfo.Foreground = brush; + try { brush = new SolidColorBrush((Color)ColorConverter.ConvertFromString(config.TabletView.LatencyColor)); } + catch (Exception) { brush = Brushes.Black; } + textLatency.Foreground = brush; + textInput.Foreground = tabletRenderer.brushInput; + textOutput.Foreground = tabletRenderer.brushOutput; + + // Text font + try + { + FontFamilyConverter fontConverter = new FontFamilyConverter(); + FontFamily fontFamily = (FontFamily)fontConverter.ConvertFromString(config.TabletView.Font); + textTabletInfo.FontFamily = fontFamily; + textInput.FontFamily = fontFamily; + textOutput.FontFamily = fontFamily; + textLatency.FontFamily = fontFamily; + } + catch (Exception) { } + + // Font size + textTabletInfo.FontSize = config.TabletView.FontSize; + textInput.FontSize = config.TabletView.FontSize; + textOutput.FontSize = config.TabletView.FontSize; + textLatency.FontSize = config.TabletView.FontSize; + + // Info text + textTabletInfo.Text = config.TabletName + " - " + + Utils.GetNumberString(config.TabletAreas[0].Width) + " x " + + Utils.GetNumberString(config.TabletAreas[0].Height) + " mm → " + + Utils.GetNumberString(config.ScreenAreas[0].Width, "0") + " x " + + Utils.GetNumberString(config.ScreenAreas[0].Height, "0") + " px"; + + + // + // Update/draw timer + // + timer = new MultimediaTimer { Interval = 2 }; + timer.Tick += UpdateTimer_Tick; + + // Last values + lastPosition = new Vector(0, 0); + lastUpdate = DateTime.Now; + lastPressure = 0; + + // Average values + velocity = 0; + latency = 0; + + + // Input loss + hadInputLoss = true; + lastInputStartTime = DateTime.Now; + + // Window events + Loaded += WindowTabletView_Loaded; + Closing += WindowTabletView_Closing; + KeyDown += WindowTabletView_KeyDown; + + MouseDown += WindowTabletView_MouseDown; + MouseUp += WindowTabletView_MouseUp; + + + // Set GC mode to low latency + GCSettings.LatencyMode = GCLatencyMode.SustainedLowLatency; + + } + + // + // Update/draw timer tick + // + private void UpdateTimer_Tick(object sender, EventArgs e) + { + // Output position haven't changed -> skip + if ( + lastPosition.X == driver.tabletState.outputX + && + lastPosition.Y == driver.tabletState.outputY + ) + { + double timeDelta = (DateTime.Now - lastUpdate).TotalMilliseconds; + + // Input loss + if (timeDelta >= 30) + { + hadInputLoss = true; + } + + // Hide renderer + if ( + config.TabletView.FadeInOut + && + timeDelta >= 200 + && + timeDelta <= 1000 + ) + { + Application.Current.Dispatcher.Invoke(() => + { + canvasTabletView.Opacity -= (timeDelta - 200) / 5000.0; + if (canvasTabletView.Opacity < 0) + canvasTabletView.Opacity = 0; + + //canvasTabletView.Opacity = 1.0 - ((timeDelta - 200.0) / 800.0); + canvasTabletView.InvalidateVisual(); + }); + } + + + return; + } + else + { + if (hadInputLoss) + { + lastInputStartTime = DateTime.Now; + hadInputLoss = false; + } + } + + // + // Update renderer positions + // + // Inverted + if (config.Invert) + { + tabletRenderer.PositionInput.X = config.TabletFullArea.Width - driver.tabletState.inputX - config.TabletAreas[0].X; + tabletRenderer.PositionInput.Y = config.TabletFullArea.Height - driver.tabletState.inputY - config.TabletAreas[0].Y; + tabletRenderer.PositionOutput.X = config.TabletFullArea.Width - driver.tabletState.outputX - config.TabletAreas[0].X; + tabletRenderer.PositionOutput.Y = config.TabletFullArea.Height - driver.tabletState.outputY - config.TabletAreas[0].Y; + } + // Normal + else + { + tabletRenderer.PositionInput.X = driver.tabletState.inputX - config.TabletAreas[0].X; + tabletRenderer.PositionInput.Y = driver.tabletState.inputY - config.TabletAreas[0].Y; + tabletRenderer.PositionOutput.X = driver.tabletState.outputX - config.TabletAreas[0].X; + tabletRenderer.PositionOutput.Y = driver.tabletState.outputY - config.TabletAreas[0].Y; + } + + // Rotate and offset positions + Point rotatedPoint = new Point(0, 0); + config.TabletAreas[0].GetRotatedPointReverse(ref rotatedPoint, tabletRenderer.PositionInput.X, tabletRenderer.PositionInput.Y); + tabletRenderer.PositionInput.X = rotatedPoint.X + config.TabletAreas[0].Width / 2.0; + tabletRenderer.PositionInput.Y = rotatedPoint.Y + config.TabletAreas[0].Height / 2.0; + + config.TabletAreas[0].GetRotatedPointReverse(ref rotatedPoint, tabletRenderer.PositionOutput.X, tabletRenderer.PositionOutput.Y); + tabletRenderer.PositionOutput.X = rotatedPoint.X + config.TabletAreas[0].Width / 2.0; + tabletRenderer.PositionOutput.Y = rotatedPoint.Y + config.TabletAreas[0].Height / 2.0; + + + // Scale to canvas + tabletRenderer.PositionInput.X *= tabletRenderer.ScaleX; + tabletRenderer.PositionInput.Y *= tabletRenderer.ScaleY; + tabletRenderer.PositionOutput.X *= tabletRenderer.ScaleX; + tabletRenderer.PositionOutput.Y *= tabletRenderer.ScaleY; + + + // Update renderer pressures + tabletRenderer.PressureInput = driver.tabletState.inputPressure; + tabletRenderer.PressureOutput = driver.tabletState.outputPressure; + + // + // Run in the main thread + // + Application.Current.Dispatcher.Invoke(() => + { + // Show renderer + if (config.TabletView.FadeInOut) + { + double timeDelta = (DateTime.Now - lastInputStartTime).TotalMilliseconds; + if (timeDelta >= 0 && timeDelta <= 800 || canvasTabletView.Opacity >= 1) + { + canvasTabletView.Opacity += timeDelta / 5000.0; + if (canvasTabletView.Opacity >= 1) + canvasTabletView.Opacity = 1; + } + else + { + canvasTabletView.Opacity = 1.0; + } + } + + // Update pen positions + tabletRenderer.UpdateLastPositions(); + + // Add draw positions + if (driver.tabletState.outputPressure > 0) + { + if (lastPressure <= 0) + { + tabletRenderer.AddDrawPosition(-1, -1); + } + tabletRenderer.AddDrawPosition(tabletRenderer.PositionOutput.X, tabletRenderer.PositionOutput.Y); + } + lastPressure = driver.tabletState.outputPressure; + + // Latency text + if (driver.tabletState.inputVelocity > 0) + { + double dx = driver.tabletState.inputX - driver.tabletState.outputX; + double dy = driver.tabletState.inputY - driver.tabletState.outputY; + double distance = Math.Sqrt(dx * dx + dy * dy); + double targetLatency = distance / driver.tabletState.inputVelocity * 1000.0; + latency += (targetLatency - latency) / 10.0; + velocity += (driver.tabletState.inputVelocity - velocity) / 20.0; + textLatency.Text = "±" + Utils.GetNumberString(latency, "0") + " ms\n" + + Utils.GetNumberString(Math.Round(velocity / 10.0) * 10, "0") + " mm/s"; + } + + // Render + tabletRenderer.InvalidateVisual(); + + // Update last values + lastPosition.X = driver.tabletState.outputX; + lastPosition.Y = driver.tabletState.outputY; + lastUpdate = DateTime.Now; + }); + + } + + + // + // Window key down + // + private void WindowTabletView_KeyDown(object sender, KeyEventArgs e) + { + if (e.Key == Key.Escape) + { + Close(); + } + if (e.Key == Key.B) + { + if (canvasTabletView.Background == Brushes.White) + canvasTabletView.Background = Brushes.Black; + else + canvasTabletView.Background = Brushes.White; + } + } + + + // + // Window loaded + // + private void WindowTabletView_Loaded(object sender, RoutedEventArgs e) + { + double scaleX, scaleY; + double aspectScreen, aspectTablet; + double newHeight; + + // Canvas default size + canvasTabletView.Width = 1280; + canvasTabletView.Height = 720; + + // Tablet renderer + scaleX = canvasTabletView.Width / config.TabletAreas[0].Width; + + // Set canvas height + newHeight = config.TabletAreas[0].Height * scaleX; + aspectScreen = config.ScreenAreas[0].Width / config.ScreenAreas[0].Height; + aspectTablet = config.TabletAreas[0].Width / config.TabletAreas[0].Height; + newHeight *= aspectTablet / aspectScreen; + scaleY = newHeight / config.TabletAreas[0].Height; + canvasTabletView.Height = newHeight; + + // Set renderer scale + tabletRenderer.ScaleX = scaleX; + tabletRenderer.ScaleY = scaleY; + + // Timer + timer.Start(); + + // Enable state output + driver.SendCommand("StateOutput true"); + } + + // + // Window closed + // + private void WindowTabletView_Closing(object sender, EventArgs e) + { + if (timer.IsRunning) + timer.Stop(); + + // Disable state output + driver.SendCommand("StateOutput false"); + + // Set GC mode back to normal + GCSettings.LatencyMode = GCLatencyMode.Interactive; + GC.Collect(10, GCCollectionMode.Forced); + } + + // + // Window mouse down + // + private void WindowTabletView_MouseDown(object sender, MouseButtonEventArgs e) + { + + // Drag move with left mouse button in borderless mode + if (config.TabletView.Borderless && e.LeftButton == MouseButtonState.Pressed) + { + DragMove(); + } + } + + // + // Window mouse up + // + private void WindowTabletView_MouseUp(object sender, MouseButtonEventArgs e) + { + // Close window with right click in borderless mode + if (config.TabletView.Borderless && e.ChangedButton == MouseButton.Right && e.RightButton == MouseButtonState.Released) + { + Close(); + } + } + } +} diff --git a/TabletDriverGUI/WindowTabletViewSettings.xaml b/TabletDriverGUI/WindowTabletViewSettings.xaml new file mode 100644 index 0000000..1858696 --- /dev/null +++ b/TabletDriverGUI/WindowTabletViewSettings.xaml @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + #FFFFFF + + + #ffffff + + + #ffffff + + + #ffffff + + + + #ffffff + + + + #ffffff + + + + 30 + + + + 30 + + + + 30 + + + + Arial + + + + 25 + + + + + + + + + 0 + 0 + + + + + + + + + + 0 + 0 + + + + Fade in/out when input is detected/lost + Borderless window + + + + + + + + diff --git a/TabletDriverGUI/WindowTabletViewSettings.xaml.cs b/TabletDriverGUI/WindowTabletViewSettings.xaml.cs new file mode 100644 index 0000000..170c590 --- /dev/null +++ b/TabletDriverGUI/WindowTabletViewSettings.xaml.cs @@ -0,0 +1,208 @@ +using System.Collections.Generic; +using System.IO; +using System.Runtime.Serialization.Formatters.Binary; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; + +namespace TabletDriverGUI +{ + /// + /// Interaction logic for TabletViewSettings.xaml + /// + public partial class WindowTabletViewSettings : Window + { + Configuration config; + + List presets; + + public WindowTabletViewSettings(Configuration config) + { + InitializeComponent(); + this.config = config; + + presets = new List + { + new Configuration.Preset("Default", (c) => + { + }), + + new Configuration.Preset("OBS Overlay", (c) => + { + Configuration.TabletViewSettings settings = c.TabletView; + settings.BackgroundColor = "#000000"; + settings.InfoColor = "#3333FF"; + settings.InputColor = "#33FF33"; + settings.OutputColor = "#FF3333"; + settings.LatencyColor = "#FFFF33"; + settings.DrawColor = "#FFFFFF"; + }), + + new Configuration.Preset("OBS Overlay 2", (c) => + { + Configuration.TabletViewSettings settings = c.TabletView; + settings.BackgroundColor = "#000000"; + settings.InfoColor = Utils.RGBToHexColor(50,100,255); + settings.InputColor = Utils.RGBToHexColor(190,255,0); + settings.OutputColor = Utils.RGBToHexColor(255,120,0); + settings.LatencyColor = Utils.RGBToHexColor(230,220,0); + settings.DrawColor = "#6666AA"; + settings.InputTrailLength = 100; + settings.OutputTrailLength = 100; + settings.DrawLength = 0; + settings.Font = "Exo 2"; + settings.FontSize = 25; + settings.OffsetText.X = 0; + settings.OffsetText.Y = 10; + settings.OffsetPressure.X = -15; + settings.OffsetPressure.Y = 12; + settings.FadeInOut = true; + }), + + new Configuration.Preset("OBS Input Cursor Only", (c) => + { + Configuration.TabletViewSettings settings = c.TabletView; + settings.BackgroundColor = "#000000"; + settings.InputColor = "#33FF33"; + settings.OutputColor = "transparent"; + settings.InputTrailLength = 100; + settings.OutputTrailLength = 0; + settings.OffsetText = new Point(0, -200); + settings.OffsetPressure = new Point(0, -200); + settings.FadeInOut = true; + }), + + new Configuration.Preset("OBS Output Cursor Only", (c) => + { + Configuration.TabletViewSettings settings = c.TabletView; + settings.BackgroundColor = "#000000"; + settings.InputColor = "transparent"; + settings.OutputColor = "#FF3333"; + settings.InputTrailLength = 0; + settings.OutputTrailLength = 100; + settings.OffsetText = new Point(0, -200); + settings.OffsetPressure = new Point(0, -200); + settings.FadeInOut = true; + }), + + + }; + + // Fill combobox + foreach (var preset in presets) + { + comboBoxPresets.Items.Add(preset); + } + comboBoxPresets.Focus(); + + LoadValues(config); + + KeyDown += WindowTabletViewSettings_KeyDown; + } + + private void WindowTabletViewSettings_KeyDown(object sender, KeyEventArgs e) + { + // Esc -> Cancel + if (e.Key == Key.Escape) + { + ButtonCancel_Click(sender, null); + } + + } + + public void LoadValues(Configuration config) + { + textBackgroundColor.Text = config.TabletView.BackgroundColor; + textInfoColor.Text = config.TabletView.InfoColor; + textInputColor.Text = config.TabletView.InputColor; + textOutputColor.Text = config.TabletView.OutputColor; + textLatencyColor.Text = config.TabletView.LatencyColor; + textDrawColor.Text = config.TabletView.DrawColor; + textInputTrailLength.Text = Utils.GetNumberString(config.TabletView.InputTrailLength); + textOutputTrailLength.Text = Utils.GetNumberString(config.TabletView.OutputTrailLength); + textDrawLength.Text = Utils.GetNumberString(config.TabletView.DrawLength); + textFont.Text = config.TabletView.Font; + textFontSize.Text = Utils.GetNumberString(config.TabletView.FontSize); + textOffsetTextX.Text = Utils.GetNumberString(config.TabletView.OffsetText.X); + textOffsetTextY.Text = Utils.GetNumberString(config.TabletView.OffsetText.Y); + textOffsetPressureX.Text = Utils.GetNumberString(config.TabletView.OffsetPressure.X); + textOffsetPressureY.Text = Utils.GetNumberString(config.TabletView.OffsetPressure.Y); + checkBoxFadeInOut.IsChecked = config.TabletView.FadeInOut; + checkBoxBorderless.IsChecked = config.TabletView.Borderless; + } + + public void StoreValues() + { + + config.TabletView.BackgroundColor = textBackgroundColor.Text; + config.TabletView.InfoColor = textInfoColor.Text; + config.TabletView.InputColor = textInputColor.Text; + config.TabletView.OutputColor = textOutputColor.Text; + config.TabletView.LatencyColor = textLatencyColor.Text; + config.TabletView.DrawColor = textDrawColor.Text; + config.TabletView.Font = textFont.Text; + + if (Utils.ParseNumber(textInputTrailLength.Text, out double value)) + config.TabletView.InputTrailLength = (int)value; + if (config.TabletView.InputTrailLength < 0) + config.TabletView.InputTrailLength = 0; + + if (Utils.ParseNumber(textOutputTrailLength.Text, out value)) + config.TabletView.OutputTrailLength = (int)value; + if (config.TabletView.OutputTrailLength < 0) + config.TabletView.OutputTrailLength = 0; + + if (Utils.ParseNumber(textDrawLength.Text, out value)) + config.TabletView.DrawLength = (int)value; + if (config.TabletView.DrawLength < 0) + config.TabletView.DrawLength = 0; + + if (Utils.ParseNumber(textFontSize.Text, out value)) + config.TabletView.FontSize = value; + if (Utils.ParseNumber(textOffsetTextX.Text, out value)) + config.TabletView.OffsetText.X = value; + if (Utils.ParseNumber(textOffsetTextY.Text, out value)) + config.TabletView.OffsetText.Y = value; + if (Utils.ParseNumber(textOffsetPressureX.Text, out value)) + config.TabletView.OffsetPressure.X = value; + if (Utils.ParseNumber(textOffsetPressureY.Text, out value)) + config.TabletView.OffsetPressure.Y = value; + config.TabletView.FadeInOut = (bool)checkBoxFadeInOut.IsChecked; + config.TabletView.Borderless = (bool)checkBoxBorderless.IsChecked; + + } + + private void ButtonSave_Click(object sender, RoutedEventArgs e) + { + StoreValues(); + DialogResult = true; + Close(); + } + + private void ButtonCancel_Click(object sender, RoutedEventArgs e) + { + DialogResult = false; + Close(); + } + + private void ComboBoxPresets_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (comboBoxPresets.SelectedItem != null) + { + Configuration tmpConfig = new Configuration(); + Configuration.Preset preset = (Configuration.Preset)comboBoxPresets.SelectedItem; + preset.Action(tmpConfig); + LoadValues(tmpConfig); + } + } + + private void ComboBoxPresets_KeyDown(object sender, KeyEventArgs e) + { + if (e.Key == Key.Enter) + { + ButtonSave_Click(sender, null); + } + } + } +} diff --git a/TabletDriverGUI/WacomArea.xaml b/TabletDriverGUI/WindowWacomArea.xaml similarity index 83% rename from TabletDriverGUI/WacomArea.xaml rename to TabletDriverGUI/WindowWacomArea.xaml index 7e8b267..67ddd25 100644 --- a/TabletDriverGUI/WacomArea.xaml +++ b/TabletDriverGUI/WindowWacomArea.xaml @@ -1,57 +1,60 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -