Repository: Phaiax/Key-n-Stroke Branch: master Commit: be9d567d2a65 Files: 71 Total size: 489.1 KB Directory structure: gitextract_xryqbs4m/ ├── .gitattributes ├── .github/ │ └── workflows/ │ └── ci.yml ├── .gitignore ├── DEVELOP.md ├── Directory.Build.props ├── KeyNStroke/ │ ├── AnnotateLine.xaml │ ├── AnnotateLine.xaml.cs │ ├── App.config │ ├── App.xaml │ ├── App.xaml.cs │ ├── ButtonIndicator1.Designer.cs │ ├── ButtonIndicator1.cs │ ├── ButtonIndicator1.resx │ ├── ButtonIndicator2.xaml │ ├── ButtonIndicator2.xaml.cs │ ├── CursorIndicator1.xaml │ ├── CursorIndicator1.xaml.cs │ ├── EnumBindingSourceExtention.cs │ ├── FodyWeavers.xml │ ├── FodyWeavers.xsd │ ├── ImageResources.cs │ ├── KeyNStroke.csproj │ ├── KeyboardHook.cs │ ├── KeyboardLayoutParser.cs │ ├── KeyboardRawEvent.cs │ ├── KeystrokeDisplay.xaml │ ├── KeystrokeDisplay.xaml.cs │ ├── KeystrokeEvent.cs │ ├── KeystrokeParser.cs │ ├── LabeledSlider.py │ ├── LabeledSlider.xaml │ ├── LabeledSlider.xaml.cs │ ├── Log.cs │ ├── MouseHook.cs │ ├── MouseRawEvent.cs │ ├── NativeMethodsDC.cs │ ├── NativeMethodsEvents.cs │ ├── NativeMethodsGWL.cs │ ├── NativeMethodsKeyboard.cs │ ├── NativeMethodsMouse.cs │ ├── NativeMethodsWindow.cs │ ├── Properties/ │ │ ├── AssemblyInfo.cs │ │ ├── Resources.Designer.cs │ │ ├── Resources.resx │ │ ├── Settings.Designer.cs │ │ └── Settings.settings │ ├── ReadShortcut.xaml │ ├── ReadShortcut.xaml.cs │ ├── ResizeButton.xaml │ ├── ResizeButton.xaml.cs │ ├── Resources/ │ │ └── updateKey.pub.xml │ ├── Settings1.xaml │ ├── Settings1.xaml.cs │ ├── SettingsStore.cs │ ├── SpecialkeysParser.cs │ ├── Themes/ │ │ └── Generic.xaml │ ├── TweenLabel.cs │ ├── UIHelper.cs │ ├── Updater/ │ │ ├── Admininstration.cs │ │ ├── Statemachine.cs │ │ ├── Updater.cs │ │ └── Utils.cs │ ├── UrlOpener.cs │ ├── Welcome.xaml │ ├── Welcome.xaml.cs │ ├── app.manifest │ └── packages.config ├── KeyNStroke.sln ├── LICENSE ├── README.md └── version.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ ############################################################################### # Set default behavior to automatically normalize line endings. ############################################################################### * text=auto ############################################################################### # Set default behavior for command prompt diff. # # This is need for earlier builds of msysgit that does not have it on by # default for csharp files. # Note: This is only used by command line ############################################################################### #*.cs diff=csharp ############################################################################### # Set the merge driver for project and solution files # # Merging from the command prompt will add diff markers to the files if there # are conflicts (Merging from VS is not affected by the settings below, in VS # the diff markers are never inserted). Diff markers may cause the following # file extensions to fail to load in VS. An alternative would be to treat # these files as binary and thus will always conflict and require user # intervention with every merge. To do so, just uncomment the entries below ############################################################################### #*.sln merge=binary #*.csproj merge=binary #*.vbproj merge=binary #*.vcxproj merge=binary #*.vcproj merge=binary #*.dbproj merge=binary #*.fsproj merge=binary #*.lsproj merge=binary #*.wixproj merge=binary #*.modelproj merge=binary #*.sqlproj merge=binary #*.wwaproj merge=binary ############################################################################### # behavior for image files # # image files are treated as binary by default. ############################################################################### #*.jpg binary #*.png binary #*.gif binary ############################################################################### # diff behavior for common document formats # # Convert binary document formats to text before diffing them. This feature # is only available from the command line. Turn it on by uncommenting the # entries below. ############################################################################### #*.doc diff=astextplain #*.DOC diff=astextplain #*.docx diff=astextplain #*.DOCX diff=astextplain #*.dot diff=astextplain #*.DOT diff=astextplain #*.pdf diff=astextplain #*.PDF diff=astextplain #*.rtf diff=astextplain #*.RTF diff=astextplain ================================================ FILE: .github/workflows/ci.yml ================================================ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. # https://github.com/microsoft/github-actions-for-desktop-apps # This continuous integration pipeline is triggered anytime a user pushes code to the repo. # This pipeline builds the project, runs unit tests, then saves the build artifact. name: Key-n-Stroke Continuous Integration # Trigger on every master branch push and pull request on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: runs-on: windows-latest env: Solution_Path: KeyNStroke.sln #Test_Project_Path: KeyNStroke.Tests\KeyNStroke.Tests.csproj App_Project_Path: KeyNStroke\KeyNStroke.csproj App_Output_Directory: KeyNStroke\bin App_Assembly: Key-n-Stroke.exe Actions_Allow_Unsecure_Commands: true # Allows AddPAth and SetEnv commands steps: - name: Checkout uses: actions/checkout@v2 with: fetch-depth: 0 # avoid shallow clone so nbgv can do its work. # Versioning - name: Use Nerdbank.GitVersioning to set version variables uses: dotnet/nbgv@master with: setAllVars: true # Install the .NET Core workload - name: Install .NET uses: actions/setup-dotnet@v1 with: dotnet-version: '5.0.x' # Add MsBuild to the PATH: https://github.com/microsoft/setup-msbuild - name: Setup MSBuild.exe uses: microsoft/setup-msbuild@v1.0.1 #- name: Execute Unit Tests # run: dotnet test $env:Test_Project_Path # Restore the application - name: Restore the application to populate the obj and packages folder run: msbuild $env:Solution_Path /t:Restore /p:RestorePackagesConfig=true /p:Configuration=$env:Configuration env: Configuration: Release # Actual build - name: Build the application run: msbuild $env:Solution_Path /t:Rebuild /p:Configuration=$env:Configuration /p:Platform="Any CPU" env: Configuration: Release # Signing # https://github.com/dlemstra/code-sign-action # https://github.com/GabrielAcostaEngler/signtool-code-sign # https://archi-lab.net/code-signing-assemblies-with-github-actions/ - name: Upload build artifacts uses: actions/upload-artifact@v1 with: name: KeyNStroke-${{env.NBGV_SemVer2}} path: ${{ env.App_Output_Directory }}\Release\ ================================================ FILE: .gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. */Thumbs.db # User-specific files *.suo *.user *.sln.docstates # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ x64/ build/ bld/ [Bb]in/ [Oo]bj/ # Roslyn cache directories *.ide/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* #NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding addin-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml ## TODO: Comment the next line if you want to checkin your ## web deploy settings but do note that will include unencrypted ## passwords #*.pubxml # NuGet Packages Directory packages/* ## TODO: If the tool you use requires repositories.config ## uncomment the next line #!packages/repositories.config # Enable "build/" folder in the NuGet Packages folder since # NuGet packages use it for MSBuild targets. # This line needs to be after the ignore of the build folder # (and the packages folder if the line above has been uncommented) !packages/build/ # Windows Azure Build Output csx/ *.build.csdef # Windows Store app package directory AppPackages/ # Others sql/ *.Cache ClientBin/ [Ss]tyle[Cc]op.* ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # LightSwitch generated files GeneratedArtifacts/ _Pvt_Extensions/ ModelManifest.xml .vs # keys *.priv.xml ================================================ FILE: DEVELOP.md ================================================ # Key'n'Stroke (previously PxKeystrokesForScreencasts) This is a little documentation about how the source code is organized and works ## How to make a new release 1. Change Version: - Twice in AssemblyInfo.cs - In project settings under "Publish" 2. Build the App in Release mode 3. Sign the file using the certum certificate - cmd.exe: `signtool.exe sign /n Open /t http://time.certum.pl/ /fd sha256 /v Key-n-Stroke.exe` 4. Verify the signature: - cmd.exe: `signtool.exe verify /pa Key-n-Stroke.exe` 4. `./Key-n-Stroke.exe --create-update-manifest` 5. Change manifest: Update description 6. `./Key-n-Stroke.exe --sign-update-manifest` 7. Copy executable into release folder 8. Commit and push 7. Upload manifest `scp updateManifest.xml $SSHSERVER:html/key-n-stroke/` ## How to prepare the development environment - Install Visual Studio 2019 - Download dependencies: - Clean the folder `packages` in the repository root - Search a msbuild.exe and run it like this: - e.g. `"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\MSBuild.exe" KeyNStroke.sln /t:Restore /p:RestorePackagesConfig=true /p:Configuration=Debug` - Then run the application using Visual Studio ## Main (Program.cs) The main function is found in Program.cs. It initializes the classes decribed below. * new KeyboardHook() * new KeystrokeParser() * new SettingsStore() * new KeystrokeDisplay() The keypress information is intercepted in KeyboardHook, passed to KeystrokeParser and then passed to KeystrokeDisplay. ## How key events are intercepted from the system In Windows, you can register a callback (also known as hook) for certain process messages like [keyboard][LowLevelKeyboardProc] and mouse events. Callbacks are registered using the function [SetWindowsHookEx(event type, callback function, ...)][SetWindowsHookEx]. This function is not part of PxKS but part of the system library user32.dll. In PxKS we only need to define how each of those system functions looks. This is done in the files NativeMethodsKeyboard.cs, NativeMethodsMouse.cs, NativeMethodsGWL.cs and NativeMethodsSWP.cs. Sometimes special data structures and constants are needed for these system functions (for example [KBDLLHOOKSTRUCT][KBDLLHOOKSTRUCT]).They are also defined in these files. ### System calls (KeyboardHook.cs KeyboardRawEvents.cs) Now back to the key events. The class that encapsulates the calls to the system functions is KeyboardHook in KeyboardHook.cs. It registers the keyboard event system callback on creation and unregisters it on disposing/deconstructing. The related functions are RegisterKeyboardHook() and UnregisterKeyboardHook(). KeyboardHook exposes the C# event KeyEvent that takes methods of delegate type KeyboardRawEventHandler. (via interface IKeyboardRawEventProvider in KeyboardRawEvent.cs). The idea is, that you just do this nice pure C# thing ``` void hook_KeyEvent(KeyboardRawEventArgs raw_e) { // process hook } IKeyboardRawEventProvider myKeyboardHook = new KeyboardHook(); hook.KeyEvent += hook_KeyEvent; ``` ... instead of dealing with the raw system library calls. The KeyboardHook class does a little bit more. It executes multiple system calls to find out which modifier keys (shift, ...) are currently pressed and appends this information to raw_e. ### Key event processing (KeystrokeParser.cs KeystrokeEvent.cs) Next, the KeyboardRawEventArgs raw_e are converted into Keystrokes. This is happening in a similar interface/event pattern. The idea is, that the KeystrokeParser gets the RawEvents as input, determines what should be displayed to the user (for example a simple letter or a more complex information like CTRL + A), and forwards the result to the next program part using a C# event. Input: The KeystrokeParser registers itself on the KeyEvent of the KeyboardHook in the constructor. Output: The KeystrokeParser exposes the C# event KeystrokeEvent that takes methods of delegate type KeystrokeEventHandler. (via interface IKeystrokeEventProvider in KeystrokeEvent.cs). During Calculation, the KeystrokeParser uses the static methods in KeyboardLayoutParser and SpecialkeysParser (can be found in the two .cs files) to do some of the conversion. The KeyboardLayoutParser wraps some other system library functions that convert raw key information to corresponding letters with respect to the chosen keyboard layout by the user (for example us QWERTY vs german QWERTZ). The SpecialkeysParser is simply a big switch statement that converts special function keys on keyboards like 'volume up' and normal keys like 'ESC' and 'tab' to text or unicode symbols like 🔊, which are then displayed in the UI. The output of the KeystrokeParser is used by the KeystrokesDisplay. [SetWindowsHookEx]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms644990%28v=vs.85%29.aspx "SetWindowsHookEx function" [LowLevelKeyboardProc]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms644985%28v=vs.85%29.aspx "LowLevelKeyboardProc callback function" [TranslateMessage]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms644955%28v=vs.85%29.aspx "TranslateMessage function" [KBDLLHOOKSTRUCT]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms644967%28v=vs.85%29.aspx "KBDLLHOOKSTRUCT structure" ## Displaying Keystrokes (KeystrokesDisplay.cs code (F7)) * displays new keys as wished by the information in KeystrokeEventArgs (k_KeystrokeEvent()) * Checks if resizing mode is activated (CheckForSettingsMode()) * allows resizing and moving of window * keeps track of the History of keystrokes (List<TweenLabel> tweenLabels) ### TweenLabel (TweenLabel.cs code (F7)) The TweenLabel is a C# System.Windows.Forms.Control that displays a line of keystrokes in the UI and is capable of fading in and out and nicely moving around in the window. ## Settings Are stored in the C# Application.UserAppDataRegistry using the wrapper functions in SettingsStore.cs. The Settings are changed in the UI window Settings.cs ## NativeMethodsSWP.cs A wrapper for calling the system library function SetWindowPos. The class NativeMethodsSWP provides a method to pin the UI on top of everything. ## NativeMethodsGWL.cs Some wrappers for calling system library functions. They provide methods to make the UI through clickable and clickable again. ## UrlOpener.cs Open a browser. ================================================ FILE: Directory.Build.props ================================================  3.4.244 all ================================================ FILE: KeyNStroke/AnnotateLine.xaml ================================================  ================================================ FILE: KeyNStroke/AnnotateLine.xaml.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel; 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.Interop; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; using static KeyNStroke.NativeMethodsMouse; namespace KeyNStroke { /// /// Interaktionslogik für AnnotateLine.xaml /// public partial class AnnotateLine : Window { IMouseRawEventProvider m; IKeystrokeEventProvider k; SettingsStore s; IntPtr windowHandle; bool isDown; POINT startCursorPosition = new POINT(0, 0); POINT endCursorPosition = new POINT(0, 0); bool nextClickDraws = false; bool nextClickHides = false; public AnnotateLine(IMouseRawEventProvider m, IKeystrokeEventProvider k, SettingsStore s) { InitializeComponent(); this.m = m; this.s = s; this.k = k; s.PropertyChanged += settingChanged; } private void Window_Loaded(object sender, RoutedEventArgs e) { s.CallPropertyChangedForAllProperties(); m.MouseEvent += m_MouseEvent; this.k.KeystrokeEvent += m_KeystrokeEvent; windowHandle = new WindowInteropHelper(this).Handle; SetFormStyles(); } #region Shortcut public string AnnotateLineShortcut; void m_KeystrokeEvent(KeystrokeEventArgs e) { if (s == null) return; string pressed = e.ShortcutIdentifier(); e.raw.preventDefault = e.raw.preventDefault || CheckForTrigger(pressed); } private bool CheckForTrigger(string pressed) { if (AnnotateLineShortcut != null && pressed == AnnotateLineShortcut) { nextClickDraws = true; return true; } return false; } public void SetAnnotateLineShortcut(string shortcut) { if (KeystrokeDisplay.ValidateShortcutSetting(shortcut)) { AnnotateLineShortcut = shortcut; } else { AnnotateLineShortcut = s.AnnotateLineShortcutDefault; } } #endregion private void m_MouseEvent(MouseRawEventArgs raw_e) { if (s == null) return; if (!isDown && nextClickHides && raw_e.Action == MouseAction.Down) { raw_e.preventDefault = true; nextClickHides = false; this.Hide(); } if (isDown && raw_e.Action == MouseAction.Move) { endCursorPosition = raw_e.Position; UpdatePositionAndSize(); } else if (!isDown && raw_e.Action == MouseAction.Down && nextClickDraws) { isDown = true; raw_e.preventDefault = true; nextClickDraws = false; startCursorPosition = raw_e.Position; endCursorPosition = raw_e.Position; } else if (isDown && raw_e.Action == MouseAction.Up) { isDown = false; nextClickHides = true; } } private void settingChanged(object sender, PropertyChangedEventArgs e) { this.Dispatcher.BeginInvoke((Action)(() => { if (s == null) return; switch (e.PropertyName) { case "AnnotateLineShortcutTrigger": nextClickDraws = true; break; case "AnnotateLineColor": UpdateColor(); break; case "AnnotateLineShortcut": SetAnnotateLineShortcut(s.AnnotateLineShortcut); break; } })); } void SetFormStyles() { Log.e("CI", $"WindowHandle={windowHandle}"); NativeMethodsGWL.ClickThrough(windowHandle); NativeMethodsGWL.HideFromAltTab(windowHandle); UpdatePositionAndSize(); } void UpdateColor() { line.Fill = new SolidColorBrush(UIHelper.ToMediaColor(s.AnnotateLineColor)); } void UpdatePositionAndSize() { if (isDown) { this.Show(); Int32 xmin = Math.Min(startCursorPosition.X, endCursorPosition.X); Int32 ymin = Math.Min(startCursorPosition.Y, endCursorPosition.Y); Int32 h = Math.Abs(startCursorPosition.Y - endCursorPosition.Y); Int32 w = Math.Abs(startCursorPosition.X - endCursorPosition.X); bool horizontal = w >= h; IntPtr monitor = NativeMethodsWindow.MonitorFromPoint(startCursorPosition, NativeMethodsWindow.MonitorOptions.MONITOR_DEFAULTTONEAREST); uint adpiX = 0, adpiY = 0; NativeMethodsWindow.GetDpiForMonitor(monitor, NativeMethodsWindow.DpiType.MDT_EFFECTIVE_DPI, ref adpiX, ref adpiY); Log.e("AL", $"apix={adpiX} adpiy={adpiY} aw={ActualWidth} ah={ActualHeight} cx={startCursorPosition.X} cy={startCursorPosition.Y}"); if (horizontal) { this.Width = w / (double)adpiX * 96.0; this.Height = 4; NativeMethodsWindow.SetWindowPosition(windowHandle, (int)xmin, (int)startCursorPosition.Y - 2); } else { this.Width = 4; this.Height = h / (double)adpiY * 96.0; NativeMethodsWindow.SetWindowPosition(windowHandle, (int)startCursorPosition.X - 2, ymin); } } else { this.Hide(); } } private void Window_Closed(object sender, EventArgs e) { if (m != null) { m.MouseEvent -= m_MouseEvent; } if (k != null) { k.KeystrokeEvent -= m_KeystrokeEvent; } if (s != null) { s.PropertyChanged -= settingChanged; } m = null; s = null; k = null; } } } ================================================ FILE: KeyNStroke/App.config ================================================  ================================================ FILE: KeyNStroke/App.xaml ================================================  ================================================ FILE: KeyNStroke/App.xaml.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel; using System.Configuration; using System.Data; using System.Drawing.Printing; using System.IO; using System.IO.IsolatedStorage; using System.Linq; using System.Runtime.Serialization.Json; using System.Text; using System.Threading.Tasks; using System.Windows; using KeyNStroke; namespace KeyNStroke { /// /// Interaktionslogik für "App.xaml" /// public partial class App : Application { #region Main() [System.STAThreadAttribute()] [System.Diagnostics.DebuggerNonUserCodeAttribute()] public static void Main() { App app = new App(); app.InitializeComponent(); app.Run(); } #endregion #region Init SettingsStore mySettings; Window welcomeWindow; Settings1 settingsWindow; protected override void OnStartup(StartupEventArgs e) { Log.SetTagFilter("UPDATE"); if (Updater.Updater.HandleArgs(e.Args)) { Shutdown(); } InitSettings(); ImageResources.Init(mySettings.ButtonIndicatorCustomIconsFolder); InitKeyboardInterception(); mySettings.PropertyChanged += OnSettingChanged; myKeystrokeConverter.KeystrokeEvent += m_KeystrokeEvent; //myUi.FormClosed += OnUiClosed; mySettings.CallPropertyChangedForAllProperties(); makeNotifyIcon(); if (mySettings.WelcomeOnStartup) { showWelcomeWindow(); } } protected override void OnActivated(EventArgs e) { } private void InitSettings() { mySettings = new SettingsStore(); mySettings.WindowLocationDefault = new Point( System.Windows.SystemParameters.PrimaryScreenWidth - mySettings.WindowSizeDefault.Width - 20, System.Windows.SystemParameters.PrimaryScreenHeight - mySettings.WindowSizeDefault.Height - 40); //mySettings.ResetAll(); // test defaults mySettings.LoadAll(); } IKeyboardRawEventProvider myKeyboardHook; IKeystrokeEventProvider myKeystrokeConverter; private void InitKeyboardInterception() { myKeyboardHook = new KeyboardHook(); myKeystrokeConverter = new KeystrokeParser(myKeyboardHook); } #endregion #region Closing/Exiting private void OnUiClosed(object sender, EventArgs e) { DisableCursorIndicator(); DisableButtonIndicator(); DisableKeystrokeHistory(); } protected override void OnExit(ExitEventArgs e) { OnUiClosed(this, e); this.notifyIcon_main.Visible = false; } #endregion #region Tray Icon private System.Windows.Forms.NotifyIcon notifyIcon_main; void makeNotifyIcon() { var _assembly = System.Reflection.Assembly.GetExecutingAssembly(); Stream iconStream = Application.GetResourceStream(new Uri("pack://application:,,,/Key-n-Stroke;component/Resources/app.ico")).Stream; var icon = new System.Drawing.Icon(iconStream); this.notifyIcon_main = new System.Windows.Forms.NotifyIcon(); this.notifyIcon_main.BalloonTipText = "xfxfn"; this.notifyIcon_main.BalloonTipTitle = "xfgn"; this.notifyIcon_main.Click += new EventHandler(notifyIcon_Click); this.notifyIcon_main.Icon = icon; this.notifyIcon_main.Visible = true; } void notifyIcon_Click(object sender, EventArgs e) { showSettingsWindow(); } #endregion #region Settings Window public void showSettingsWindow() { if (settingsWindow == null) { settingsWindow = new Settings1(mySettings, myKeystrokeConverter); settingsWindow.Show(); } else { settingsWindow.Activate(); } } public void onSettingsWindowClosed() { settingsWindow = null; } #endregion #region Welcome Window public void showWelcomeWindow() { if (welcomeWindow == null) { welcomeWindow = new Welcome(mySettings); welcomeWindow.Show(); } else { welcomeWindow.Activate(); } } public void onWelcomeWindowClosed() { welcomeWindow = null; } #endregion #region Shortcut public string StandbyShortcut; void m_KeystrokeEvent(KeystrokeEventArgs e) { string pressed = e.ShortcutIdentifier(); e.raw.preventDefault = e.raw.preventDefault || CheckForTrigger(pressed); } private bool CheckForTrigger(string pressed) { if (StandbyShortcut != null && pressed == StandbyShortcut) { mySettings.Standby = !mySettings.Standby; return true; } return false; } public void SetStandbyShortcut(string shortcut) { if (KeystrokeDisplay.ValidateShortcutSetting(shortcut)) { StandbyShortcut = shortcut; } else { StandbyShortcut = mySettings.StandbyShortcutDefault; } } #endregion #region OnSettingChanged private void OnSettingChanged(object sender, PropertyChangedEventArgs e) { switch (e.PropertyName) { case "EnableCursorIndicator": OnCursorIndicatorSettingChanged(); break; case "ButtonIndicator": OnButtonIndicatorSettingChanged(); break; case "EnableKeystrokeHistory": OnKeystrokeHistorySettingChanged(); break; case "EnableAnnotateLine": OnAnnotateLineSettingChanged(); break; case "ButtonIndicatorCustomIconsFolder": case "ButtonIndicatorUseCustomIcons": if (mySettings.ButtonIndicatorUseCustomIcons) { ImageResources.ReloadRessources(mySettings.ButtonIndicatorCustomIconsFolder); } else { ImageResources.ReloadRessources(null); } break; case "StandbyShortcut": SetStandbyShortcut(mySettings.StandbyShortcut); break; case "Standby": OnCursorIndicatorSettingChanged(); OnButtonIndicatorSettingChanged(); OnKeystrokeHistorySettingChanged(); OnAnnotateLineSettingChanged(); break; } } #endregion #region Keystroke History KeystrokeDisplay KeystrokeHistoryWindow; bool KeystrokeHistoryVisible; private void OnKeystrokeHistorySettingChanged() { if (mySettings.EnableKeystrokeHistory && !mySettings.Standby) { EnableKeystrokeHistory(); } else { DisableKeystrokeHistory(); } } private void EnableKeystrokeHistory() { if (KeystrokeHistoryVisible || KeystrokeHistoryWindow != null) return; KeystrokeHistoryVisible = true; // Prevent Recursive call KeystrokeHistoryWindow = new KeystrokeDisplay(myKeystrokeConverter, mySettings); KeystrokeHistoryWindow.Show(); } private void DisableKeystrokeHistory() { KeystrokeHistoryVisible = false; if (KeystrokeHistoryWindow == null) return; KeystrokeHistoryWindow.Close(); KeystrokeHistoryWindow = null; } #endregion #region Button Indicator ButtonIndicator1 ButtonIndicatorWindow = null; private void OnButtonIndicatorSettingChanged() { if (mySettings.ButtonIndicator != ButtonIndicatorType.Disabled && !mySettings.Standby) { EnableButtonIndicator(); } else { DisableButtonIndicator(); } } private void EnableButtonIndicator() { if (ButtonIndicatorWindow != null) return; Log.e("BI", "EnableButtonIndicator"); EnableMouseHook(); ButtonIndicatorWindow = new ButtonIndicator1(myMouseHook, myKeystrokeConverter, mySettings); ButtonIndicatorWindow.Show(); } private void DisableButtonIndicator() { if (ButtonIndicatorWindow == null) return; ButtonIndicatorWindow.Close(); ButtonIndicatorWindow = null; DisableMouseHookIfNotNeeded(); Log.e("BI", "DisableButtonIndicator"); } #endregion #region Cursor Indicator CursorIndicator1 CursorIndicatorWindow = null; private void OnCursorIndicatorSettingChanged() { if (mySettings.EnableCursorIndicator && !mySettings.Standby) { EnableCursorIndicator(); } else { DisableCursorIndicator(); } } private void EnableCursorIndicator() { if (CursorIndicatorWindow != null) return; Log.e("CI", "EnableCursorIndicator"); EnableMouseHook(); CursorIndicatorWindow = new CursorIndicator1(myMouseHook, mySettings); CursorIndicatorWindow.Show(); } private void DisableCursorIndicator() { if (CursorIndicatorWindow == null) return; CursorIndicatorWindow.Close(); CursorIndicatorWindow = null; DisableMouseHookIfNotNeeded(); Log.e("CI", "DisableCursorIndicator"); } #endregion #region Annotate Line AnnotateLine AnnotateLineWindow; private void OnAnnotateLineSettingChanged() { if (mySettings.EnableAnnotateLine && !mySettings.Standby) { EnableAnnotateLine(); } else { DisableAnnotateLine(); } } private void EnableAnnotateLine() { if (AnnotateLineWindow != null) return; Log.e("AL", "EnableAnnotateLineWindow"); EnableMouseHook(); AnnotateLineWindow = new AnnotateLine(myMouseHook, myKeystrokeConverter, mySettings); AnnotateLineWindow.Show(); } private void DisableAnnotateLine() { if (AnnotateLineWindow == null) return; AnnotateLineWindow.Close(); AnnotateLineWindow = null; DisableMouseHookIfNotNeeded(); Log.e("AL", "DisableAnnotateLineWindow"); } #endregion #region Mouse Hook IMouseRawEventProvider myMouseHook = null; private void EnableMouseHook() { if (myMouseHook != null) return; myMouseHook = new MouseHook(mySettings); } private void DisableMouseHookIfNotNeeded() { if (CursorIndicatorWindow == null && ButtonIndicatorWindow == null && AnnotateLineWindow == null) DisableMouseHook(); } private void DisableMouseHook() { if (myMouseHook == null) return; myMouseHook.Dispose(); myMouseHook = null; } #endregion } } ================================================ FILE: KeyNStroke/ButtonIndicator1.Designer.cs ================================================ namespace KeyNStroke { partial class ButtonIndicator1 { /// /// Required designer variable. /// private System.ComponentModel.IContainer components = null; /// /// Clean up any resources being used. /// /// true if managed resources should be disposed; otherwise, false. protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } #region Windows Form Designer generated code /// /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// private void InitializeComponent() { this.SuspendLayout(); // // ButtonIndicator // this.AutoScaleDimensions = new System.Drawing.SizeF(11F, 24F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(521, 482); this.Margin = new System.Windows.Forms.Padding(6, 6, 6, 6); this.Name = "ButtonIndicator"; this.ShowInTaskbar = false; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; this.Text = "CursorIndicator"; this.Load += new System.EventHandler(this.ButtonIndicator_Load); this.ResumeLayout(false); } #endregion } } ================================================ FILE: KeyNStroke/ButtonIndicator1.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using KeyNStroke; namespace KeyNStroke { public partial class ButtonIndicator1 : Form { IMouseRawEventProvider m; IKeystrokeEventProvider k; SettingsStore s; ImageResources.ComposeOptions c; Size bitmapsize; public ButtonIndicator1(IMouseRawEventProvider m, IKeystrokeEventProvider k, SettingsStore s) { InitializeComponent(); StartImageResizeThread(); this.m = m; this.s = s; this.k = k; FormClosed += ButtonIndicator_FormClosed; HideMouseIfNoButtonPressed(); c.dpi = 96; UpdatePosition(NativeMethodsMouse.CursorPosition); NativeMethodsWindow.SetWindowTopMost(this.Handle); SetFormStyles(); m.MouseEvent += m_MouseEvent; k.KeystrokeEvent += k_KeystrokeEvent; s.PropertyChanged += settingChanged; DoubleClickIconTimer.Tick += leftDoubleClickIconTimeout_Tick; DoubleClickIconTimer.Interval = 750; WheelIconTimer.Interval = 750; WheelIconTimer.Tick += WheelIconTimer_Tick; UpdateSize(); // will call Redraw() after 200ms and scaling the images //Redraw(); } private void Redraw() { Bitmap scaledAndComposedBitmap = ImageResources.Compose(c); this.bitmapsize = scaledAndComposedBitmap.Size; IntPtr handle; try { handle = this.Handle; // May be already disposed if leftDoubleClickIconTimeout triggers } catch (System.ObjectDisposedException) { return; // throw; } NativeMethodsDC.SetBitmapForWindow(handle, this.Location, scaledAndComposedBitmap, 1.0f); // opacity NativeMethodsWindow.PrintDpiAwarenessInfo(); } private void ButtonIndicator_Load(object sender, EventArgs e) { Log.e("SOME", "ButtonIndicator => Load"); RecalcOffset(); UpdateSize(); } private void k_KeystrokeEvent(KeystrokeEventArgs e) { if (s == null) return; bool changed = false; if (s.ButtonIndicatorShowModifiers) { if (e.StrokeType == KeystrokeType.Modifiers) { if (e.Shift != c.addMShift) changed = true; c.addMShift = e.Shift; if (e.Ctrl != c.addMCtrl) changed = true; c.addMCtrl = e.Ctrl; if (e.Alt != c.addMAlt) changed = true; c.addMAlt = e.Alt; if (e.Win != c.addMWin) changed = true; c.addMWin = e.Win; } } else { if (c.addMShift) changed = true; c.addMShift = false; if (c.addMCtrl) changed = true; c.addMCtrl = false; if (c.addMAlt) changed = true; c.addMAlt = false; if (c.addMWin) changed = true; c.addMWin = false; } if (changed) { Redraw(); } } MouseRawEventArgs lastDblClk; void m_MouseEvent(MouseRawEventArgs raw_e) { if (s == null) return; switch (raw_e.Action) { case MouseAction.Up: HideButton(raw_e); break; case MouseAction.Down: ShowButton(raw_e.Button); break; case MouseAction.DblClk: lastDblClk = raw_e; IndicateDoubleClick(raw_e.Button); break; case MouseAction.Move: UpdatePosition(raw_e.Position); break; case MouseAction.Wheel: IndicateWheel(raw_e); break; default: break; } } System.Windows.Forms.Timer WheelIconTimer = new System.Windows.Forms.Timer(); private void IndicateWheel(MouseRawEventArgs raw_e) { c.addBMouse = true; Log.e("WHEEL", "Display " + raw_e.wheelDelta.ToString()); WheelIconTimer.Stop(); WheelIconTimer.Start(); if (raw_e.wheelDelta < 0) { c.addBWheelDown = true; c.addBWheelUp = false; } else if (raw_e.wheelDelta > 0) { c.addBWheelUp = true; c.addBWheelDown = false; } Redraw(); } void WheelIconTimer_Tick(object sender, EventArgs e) { WheelIconTimer.Stop(); Log.e("WHEEL", "Hide "); c.addBWheelDown = false; c.addBWheelUp = false; HideMouseIfNoButtonPressed(); Redraw(); } private void IndicateDoubleClick(MouseButton mouseButton) { switch (mouseButton) { case MouseButton.LButton: c.addBMouse = true; c.addBLeft = false; c.addBLeftDouble = true; doubleClickReleased = false; Redraw(); break; case MouseButton.RButton: break; default: break; } } void leftDoubleClickIconTimeout_Tick(object sender, EventArgs e) { ((System.Windows.Forms.Timer)sender).Stop(); c.addBLeftDouble = false; HideMouseIfNoButtonPressed(); Redraw(); } private void ShowButton(MouseButton mouseButton) { switch (mouseButton) { case MouseButton.LButton: c.addBMouse = true; c.addBLeft = true; c.addBLeftDouble = false; //UpdatePosition(); Redraw(); break; case MouseButton.RButton: c.addBMouse = true; c.addBRight = true; //UpdatePosition(); Redraw(); break; case MouseButton.MButton: c.addBMouse = true; c.addBMiddle = true; //UpdatePosition(); Redraw(); break; case MouseButton.XButton: break; case MouseButton.None: break; default: break; } } System.Windows.Forms.Timer DoubleClickIconTimer = new System.Windows.Forms.Timer(); bool doubleClickReleased = true; private void HideButton(MouseRawEventArgs raw_e) { switch (raw_e.Button) { case MouseButton.LButton: c.addBLeft = false; doubleClickReleased = true; if (c.addBLeftDouble) { if (raw_e.Msllhookstruct.time - lastDblClk.Msllhookstruct.time > DoubleClickIconTimer.Interval) { c.addBLeftDouble = false; } else { DoubleClickIconTimer.Stop(); DoubleClickIconTimer.Start(); } } break; case MouseButton.RButton: c.addBRight = false; break; case MouseButton.MButton: c.addBMiddle = false; break; case MouseButton.XButton: break; case MouseButton.None: break; default: break; } HideMouseIfNoButtonPressed(); Redraw(); } void HideMouseIfNoButtonPressed() { if ( !c.addBLeft && !c.addBRight && !c.addBMiddle && !c.addBLeftDouble && !c.addBRightDouble && !c.addBWheelDown && !c.addBWheelUp) { c.addBMouse = false; } } void ButtonIndicator_FormClosed(object sender, FormClosedEventArgs e) { if (m != null) m.MouseEvent -= m_MouseEvent; if (s != null) s.PropertyChanged -= settingChanged; if (k != null) k.KeystrokeEvent -= k_KeystrokeEvent; if (newScalingFactors != null) newScalingFactors.CompleteAdding(); m = null; s = null; } void SetFormStyles() { this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; //this.Opacity = 0.8; NativeMethodsGWL.ClickThrough(this.Handle); // This also makes the window style WS_EX_LAYERED NativeMethodsGWL.HideFromAltTab(this.Handle); UpdateSize(); UpdatePosition(NativeMethodsMouse.CursorPosition); } Thread imagesResizeThread; BlockingCollection newScalingFactors = new BlockingCollection(new ConcurrentQueue()); private void StartImageResizeThread() { imagesResizeThread = new Thread(new ThreadStart(BackgroundResizeImages)); imagesResizeThread.Start(); } void BackgroundResizeImages() { while (true) { try { double scalingFactor = newScalingFactors.Take(); // Blocks until at least one new Item is available Thread.Sleep(200); // wait for more data, so we don't do too eager calculations // skip all elements except for the last while (newScalingFactors.Count > 0) { scalingFactor = newScalingFactors.Take(); } Log.e("BI", $"size change applied: {scalingFactor}"); ImageResources.ApplyScalingFactor(scalingFactor); this.Invoke((Action) (() => { Redraw(); UpdatePosition(NativeMethodsMouse.CursorPosition); })); } catch (InvalidOperationException) { // Window closed Log.e("BI", "BackgrounResizeThread stopped"); return; } } } void UpdateSize() { newScalingFactors.Add(s.ButtonIndicatorScaling); } Size offset = new Size(0, 0); void RecalcOffset() { offset.Width = (int)(s.ButtonIndicatorPositionDistance * Math.Sin(s.ButtonIndicatorPositionAngle / 10.0f)); offset.Height = (int)(s.ButtonIndicatorPositionDistance * Math.Cos(s.ButtonIndicatorPositionAngle / 10.0f)); } void UpdatePosition(NativeMethodsMouse.POINT cursorPosition) { if (OnlyDblClkIconVisible() && doubleClickReleased) return; IntPtr monitor = NativeMethodsWindow.MonitorFromPoint(cursorPosition, NativeMethodsWindow.MonitorOptions.MONITOR_DEFAULTTONEAREST); uint adpiX = 0, adpiY = 0; NativeMethodsWindow.GetDpiForMonitor(monitor, NativeMethodsWindow.DpiType.MDT_EFFECTIVE_DPI, ref adpiX, ref adpiY); c.dpi = adpiX; NativeMethodsWindow.SetWindowPosition(this.Handle, cursorPosition.X - this.bitmapsize.Width/2 + offset.Width, cursorPosition.Y - this.bitmapsize.Height/2 + offset.Height); } private bool OnlyDblClkIconVisible() { return !c.addBLeft && !c.addBRight && !c.addBMiddle && !c.addBWheelUp && !c.addBWheelDown && (c.addBLeftDouble || c.addBRightDouble); } private void settingChanged(object sender, PropertyChangedEventArgs e) { if (s == null) return; // Log.e("BI", $"ButtonIndicator => settingChanged {e.PropertyName}"); switch (e.PropertyName) { case "ButtonIndicator": break; case "ButtonIndicatorPositionAngle": RecalcOffset(); UpdatePosition(NativeMethodsMouse.CursorPosition); break; case "ButtonIndicatorPositionDistance": RecalcOffset(); UpdatePosition(NativeMethodsMouse.CursorPosition); break; case "ButtonIndicatorScaling": UpdateSize(); break; } } } } ================================================ FILE: KeyNStroke/ButtonIndicator1.resx ================================================  text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 ================================================ FILE: KeyNStroke/ButtonIndicator2.xaml ================================================  ================================================ FILE: KeyNStroke/ButtonIndicator2.xaml.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; 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.Interop; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; using System.Windows.Threading; namespace KeyNStroke { /// /// Interaktionslogik für ButtonIndicator2.xaml /// public partial class ButtonIndicator2 : Window { IMouseRawEventProvider m; SettingsStore s; ImageResources.ComposeOptions c; IntPtr handle; public ButtonIndicator2(IMouseRawEventProvider m, SettingsStore s) { InitializeComponent(); this.m = m; this.s = s; this.Closed += this.Window_Closed; this.Loaded += Window_Loaded; } private void Window_Loaded(object sender, RoutedEventArgs e) { this.handle = new WindowInteropHelper(this).Handle; HideMouseIfNoButtonPressed(); c.dpi = 96; UpdatePosition(); NativeMethodsWindow.SetWindowTopMost(handle); SetFormStyles(); m.MouseEvent += m_MouseEvent; s.PropertyChanged += settingChanged; DoubleClickIconTimer.Tick += leftDoubleClickIconTimeout_Tick; DoubleClickIconTimer.Interval = TimeSpan.FromMilliseconds(750.0); WheelIconTimer.Interval = TimeSpan.FromMilliseconds(750.0); WheelIconTimer.Tick += WheelIconTimer_Tick; Redraw(); } protected override void OnRender(DrawingContext drawingContext) { base.OnRender(drawingContext); } private void Redraw() { Bitmap scaledAndComposedBitmap = ImageResources.Compose(c); Log.e("DPI", $"DPI at Redraw: {c.dpi}"); NativeMethodsDC.SetBitmapForWindow(this.handle, new System.Drawing.Point((int)this.Left, (int)this.Top), scaledAndComposedBitmap, 1.0f); // opacity } private void ButtonIndicator_Load(object sender, EventArgs e) { Log.e("SOME", "ButtonIndicator => Load"); RecalcOffset(); UpdateSize(); } //System.Drawing.Point cursorPosition; MouseRawEventArgs lastDblClk; void m_MouseEvent(MouseRawEventArgs raw_e) { // cursorPosition = raw_e.Position; this position does not work nice with multiple monitor/dpi setups switch (raw_e.Action) { case MouseAction.Up: HideButton(raw_e); break; case MouseAction.Down: ShowButton(raw_e.Button); break; case MouseAction.DblClk: lastDblClk = raw_e; IndicateDoubleClick(raw_e.Button); break; case MouseAction.Move: UpdatePosition(); break; case MouseAction.Wheel: IndicateWheel(raw_e); break; default: break; } } DispatcherTimer WheelIconTimer = new DispatcherTimer(); private void IndicateWheel(MouseRawEventArgs raw_e) { c.addBMouse = true; Log.e("WHEEL", "Display " + raw_e.wheelDelta.ToString()); WheelIconTimer.Stop(); WheelIconTimer.Start(); if (raw_e.wheelDelta < 0) { c.addBWheelDown = true; c.addBWheelUp = false; } else if (raw_e.wheelDelta > 0) { c.addBWheelUp = true; c.addBWheelDown = false; } Redraw(); } void WheelIconTimer_Tick(object sender, EventArgs e) { WheelIconTimer.Stop(); Log.e("WHEEL", "Hide "); c.addBWheelDown = false; c.addBWheelUp = false; HideMouseIfNoButtonPressed(); Redraw(); } private void IndicateDoubleClick(MouseButton mouseButton) { switch (mouseButton) { case MouseButton.LButton: c.addBMouse = true; c.addBLeft = false; c.addBLeftDouble = true; doubleClickReleased = false; Redraw(); break; case MouseButton.RButton: break; default: break; } } void leftDoubleClickIconTimeout_Tick(object sender, EventArgs e) { ((DispatcherTimer)sender).Stop(); c.addBLeftDouble = false; HideMouseIfNoButtonPressed(); Redraw(); } private void ShowButton(MouseButton mouseButton) { switch (mouseButton) { case MouseButton.LButton: c.addBMouse = true; c.addBLeft = true; c.addBLeftDouble = false; UpdatePosition(); Redraw(); break; case MouseButton.RButton: c.addBMouse = true; c.addBRight = true; UpdatePosition(); Redraw(); break; case MouseButton.MButton: c.addBMouse = true; c.addBMiddle = true; UpdatePosition(); Redraw(); break; case MouseButton.XButton: break; case MouseButton.None: break; default: break; } } DispatcherTimer DoubleClickIconTimer = new DispatcherTimer(); bool doubleClickReleased = true; private void HideButton(MouseRawEventArgs raw_e) { switch (raw_e.Button) { case MouseButton.LButton: c.addBLeft = false; doubleClickReleased = true; if (c.addBLeftDouble) { if (raw_e.Msllhookstruct.time - lastDblClk.Msllhookstruct.time > DoubleClickIconTimer.Interval.TotalMilliseconds) { c.addBLeftDouble = false; } else { DoubleClickIconTimer.Stop(); DoubleClickIconTimer.Start(); } } break; case MouseButton.RButton: c.addBRight = false; break; case MouseButton.MButton: c.addBMiddle = false; break; case MouseButton.XButton: break; case MouseButton.None: break; default: break; } HideMouseIfNoButtonPressed(); Redraw(); } void HideMouseIfNoButtonPressed() { if (!c.addBLeft && !c.addBRight && !c.addBMiddle && !c.addBLeftDouble && !c.addBRightDouble && !c.addBWheelDown && !c.addBWheelUp) { c.addBMouse = false; } } private void Window_Closed(object sender, EventArgs e) { if (m != null) m.MouseEvent -= m_MouseEvent; if (s != null) s.PropertyChanged -= settingChanged; m = null; s = null; } void SetFormStyles() { //this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; //this.Opacity = 0.8; NativeMethodsGWL.ClickThrough(this.handle); // This also makes the window style WS_EX_LAYERED NativeMethodsGWL.HideFromAltTab(this.handle); UpdateSize(); UpdatePosition(); } void UpdateSize() { ImageResources.ApplyScalingFactor(s.ButtonIndicatorScalingPercentage / 100.0f); Log.e("BI", "size change"); Redraw(); } System.Drawing.Size offset = new System.Drawing.Size(0, 0); // dpi independend void RecalcOffset() { offset.Width = (int)(s.ButtonIndicatorPositionDistance * Math.Sin(s.ButtonIndicatorPositionAngle / 10.0f)); offset.Height = (int)(s.ButtonIndicatorPositionDistance * Math.Cos(s.ButtonIndicatorPositionAngle / 10.0f)); } void UpdatePosition() { if (OnlyDblClkIconVisible() && doubleClickReleased) return; NativeMethodsMouse.POINT cursorPosition = new NativeMethodsMouse.POINT(0, 0); NativeMethodsWindow.GetCursorPos(ref cursorPosition); IntPtr monitor = NativeMethodsWindow.MonitorFromPoint(cursorPosition, NativeMethodsWindow.MonitorOptions.MONITOR_DEFAULTTONEAREST); // Angular DPI seems to work best //uint edpiX = 0, edpiY = 0; //NativeMethodsWindow.GetDpiForMonitor(monitor, NativeMethodsWindow.DpiType.MDT_EFFECTIVE_DPI, ref edpiX, ref edpiY); uint adpiX = 0, adpiY = 0; NativeMethodsWindow.GetDpiForMonitor(monitor, NativeMethodsWindow.DpiType.MDT_ANGULAR_DPI, ref adpiX, ref adpiY); //uint rdpiX = 0, rdpiY = 0; //NativeMethodsWindow.GetDpiForMonitor(monitor, NativeMethodsWindow.DpiType.MDT_RAW_DPI, ref rdpiX, ref rdpiY); c.dpi = adpiX; NativeMethodsWindow.SetWindowPosition(this.handle, cursorPosition.X, cursorPosition.Y); } private bool OnlyDblClkIconVisible() { return !c.addBLeft && !c.addBRight && !c.addBMiddle && !c.addBWheelUp && !c.addBWheelDown && (c.addBLeftDouble || c.addBRightDouble); } private void settingChanged(object sender, PropertyChangedEventArgs e) { Log.e("SOME", "ButtonIndicator => settingChanged"); switch (e.PropertyName) { case "ButtonIndicator": break; case "ButtonIndicatorPositionAngle": RecalcOffset(); UpdatePosition(); break; case "ButtonIndicatorPositionDistance": RecalcOffset(); UpdatePosition(); break; case "ButtonIndicatorScalingPercentage": UpdateSize(); UpdatePosition(); break; } } } } ================================================ FILE: KeyNStroke/CursorIndicator1.xaml ================================================  ================================================ FILE: KeyNStroke/CursorIndicator1.xaml.cs ================================================ using System; using System.ComponentModel; using System.Windows; using System.Windows.Interop; using System.Windows.Media; using static KeyNStroke.NativeMethodsMouse; namespace KeyNStroke { /// /// Interaktionslogik für CursorIndicator1.xaml /// public partial class CursorIndicator1 : Window { IMouseRawEventProvider m; SettingsStore s; IntPtr windowHandle; bool isHidden; bool isDown; public CursorIndicator1(IMouseRawEventProvider m, SettingsStore s) { InitializeComponent(); this.m = m; this.s = s; s.PropertyChanged += settingChanged; this.isHidden = false; } private void Window_Loaded(object sender, RoutedEventArgs e) { m.MouseEvent += m_MouseEvent; m.CursorEvent += m_CursorEvent; windowHandle = new WindowInteropHelper(this).Handle; SetFormStyles(); } void m_MouseEvent(MouseRawEventArgs raw_e) { if (raw_e.Action == MouseAction.Move) { UpdatePosition(raw_e.Position); } else if (raw_e.Action == MouseAction.Down || raw_e.Action == MouseAction.DblClk) { isDown = true; UpdateColor(); } else if (raw_e.Action == MouseAction.Up) { isDown = false; UpdateColor(); } } void m_CursorEvent(bool visible) { if (visible) { if (isHidden) { this.Show(); isHidden = false; } } else { if (!isHidden) { this.Hide(); isHidden = true; } } } void SetFormStyles() { //this.Opacity = s.CursorIndicatorOpacity; Log.e("CI", $"WindowHandle={windowHandle}"); NativeMethodsGWL.ClickThrough(windowHandle); NativeMethodsGWL.HideFromAltTab(windowHandle); UpdateSize(); UpdateColor(); UpdatePosition(NativeMethodsMouse.CursorPosition); } void UpdateSize() { this.Width = s.CursorIndicatorSize; this.Height = s.CursorIndicatorSize; } void UpdatePosition(NativeMethodsMouse.POINT cursorPosition) { IntPtr monitor = NativeMethodsWindow.MonitorFromPoint(cursorPosition, NativeMethodsWindow.MonitorOptions.MONITOR_DEFAULTTONEAREST); uint adpiX = 0, adpiY = 0; NativeMethodsWindow.GetDpiForMonitor(monitor, NativeMethodsWindow.DpiType.MDT_EFFECTIVE_DPI, ref adpiX, ref adpiY); Log.e("CI", $"apix={adpiX} adpiy={adpiY} aw={ActualWidth} ah={ActualHeight} cx={cursorPosition.X} cy={cursorPosition.Y}"); NativeMethodsWindow.SetWindowPosition(windowHandle, (int)(cursorPosition.X - (this.ActualWidth / 2) * (double)adpiX / 96.0), (int)(cursorPosition.Y - (this.ActualHeight / 2) * (double)adpiY / 96.0)); } private void settingChanged(object sender, PropertyChangedEventArgs e) { this.Dispatcher.BeginInvoke((Action) (() => { if (s == null) return; switch (e.PropertyName) { case "EnableCursorIndicator": break; case "CursorIndicatorOpacity": UpdateColor(); break; case "CursorIndicatorSize": UpdateSize(); break; case "CursorIndicatorColor": UpdateColor(); break; case "CursorIndicatorEdgeColor": UpdateColor(); break; case "CursorIndicatorEdgeStrokeThickness": UpdateColor(); break; case "CursorIndicatorDrawEdge": UpdateColor(); break; } })); } private void UpdateColor() { Color c; if (isDown && s.CursorIndicatorFlashOnClick) { c = UIHelper.ToMediaColor(s.CursorIndicatorClickColor); } else { c = UIHelper.ToMediaColor(s.CursorIndicatorColor); } circle.Fill = new SolidColorBrush(Color.FromArgb((byte)(255 * (1 - s.CursorIndicatorOpacity)), c.R, c.G, c.B)); if (s.CursorIndicatorDrawEdge) { circle.Stroke = new SolidColorBrush(UIHelper.ToMediaColor(s.CursorIndicatorEdgeColor)); circle.StrokeThickness = s.CursorIndicatorEdgeStrokeThickness; } else { circle.Stroke = null; circle.StrokeThickness = 0; } } private void Window_Closed(object sender, EventArgs e) { if (m != null) { m.MouseEvent -= m_MouseEvent; m.CursorEvent -= m_CursorEvent; } if (s != null) s.PropertyChanged -= settingChanged; m = null; s = null; } } } ================================================ FILE: KeyNStroke/EnumBindingSourceExtention.cs ================================================ using System; using System.Collections.Generic; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Markup; namespace KeyNStroke { public class EnumBindingSourceExtention : MarkupExtension { public Type EnumType { get; private set; } public EnumBindingSourceExtention(Type enumType) { if (enumType == null || !enumType.IsEnum) throw new Exception("enumType must be of type enum"); this.EnumType = enumType; } public static T GetAttributeOfType(Enum enumVal) where T : System.Attribute { var type = enumVal.GetType(); var memInfo = type.GetMember(Enum.GetName(type, enumVal)); // type.GetMember(enumVal.ToString()); var attributes = memInfo[0].GetCustomAttributes(typeof(T), false); return (attributes.Length > 0) ? (T)attributes[0] : null; } public static string GetAttributeDescription(Enum enumValue) { var attribute = GetAttributeOfType(enumValue); return attribute == null ? String.Empty : attribute.Description; } public override object ProvideValue(IServiceProvider serviceProvider) { Array variants = Enum.GetValues(EnumType); List b = new List(variants.Length); foreach (var a in variants) { ComboBoxItem i = new ComboBoxItem(); i.Content = GetAttributeDescription((Enum)a); // new TextBlock(new Run("Hi")); i.Tag = a; b.Add(i); } return b; } } } ================================================ FILE: KeyNStroke/FodyWeavers.xml ================================================  ================================================ FILE: KeyNStroke/FodyWeavers.xsd ================================================  A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks. A list of (.NET Core) runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks A list of (.NET Core) runtime assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks. A list of unmanaged 32 bit assembly names to include, delimited with line breaks. A list of unmanaged 64 bit assembly names to include, delimited with line breaks. The order of preloaded assemblies, delimited with line breaks. This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file. Controls if .pdbs for reference assemblies are also embedded. Controls if (.NET Core) runtime assemblies are also embedded. Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option. As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off. Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code. Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior. A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with | A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |. A list of (.NET Core) runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with | A list of (.NET Core) runtime assembly names to include from the default action of "embed all Copy Local references", delimited with |. A list of unmanaged 32 bit assembly names to include, delimited with |. A list of unmanaged 64 bit assembly names to include, delimited with |. The order of preloaded assemblies, delimited with |. 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. A comma-separated list of error codes that can be safely ignored in assembly verification. 'false' to turn off automatic generation of the XML Schema file. ================================================ FILE: KeyNStroke/ImageResources.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.IO; using System.Reflection; using System.Drawing; using System.Drawing.Imaging; using System.Drawing.Drawing2D; using KeyNStroke; using System.Windows.Forms.VisualStyles; using System.Windows; namespace KeyNStroke { public class ImageResources { static Assembly _assembly; private struct BitmapCollection { public Bitmap BMouse; public Bitmap BLeft; public Bitmap BRight; public Bitmap BMiddle; public Bitmap BLeftDouble; public Bitmap BRightDouble; public Bitmap BWheelUp; public Bitmap BWheelDown; public Bitmap MCtrl; public Bitmap MWin; public Bitmap MAlt; public Bitmap MShift; } static Dictionary ScaledByDpi; static BitmapCollection Orig; // original size public static void Init(string customIconFolder) { try { _assembly = Assembly.GetExecutingAssembly(); foreach (string i in _assembly.GetManifestResourceNames()) { Log.e("RES", i); } ReloadRessources(customIconFolder); } catch { Log.e("RES", "Error accessing resources!"); } ApplyScalingFactor(1.0f); } public static void ExportBuiltinRessources(string exportFolder) { string[] filenames = { "mouse.svg", "mouse.png", "mouse_left.png", "mouse_right.png", "mouse_middle.png", "mouse_left_double.png", "mouse_right_double.png", "mouse_wheel_up.png", "mouse_wheel_down.png", "mouse_modifier_ctrl.png", "mouse_modifier_win.png", "mouse_modifier_alt.png", "mouse_modifier_shift.png" }; foreach (string png_name in filenames) { try { string target = Path.Combine(exportFolder, png_name); if (File.Exists(target)) { var result = MessageBox.Show($"Overwrite {png_name}?", $"File already exists", MessageBoxButton.YesNoCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel); if (result == MessageBoxResult.Cancel) { return; } else if (result == MessageBoxResult.No) { continue; } else { // Yes -> Overwrite } } using (var fs = new FileStream(target, FileMode.Create, FileAccess.Write)) { _assembly.GetManifestResourceStream($"KeyNStroke.Resources.{png_name}").CopyTo(fs); } } catch (Exception) { } } } public static void ReloadRessources(string customIconFolder) { // FIXME: This happens three times on startup. Orig.BMouse = LoadRessource(customIconFolder, "mouse.png"); Orig.BLeft = LoadRessource(customIconFolder, "mouse_left.png"); Orig.BRight = LoadRessource(customIconFolder, "mouse_right.png"); Orig.BMiddle = LoadRessource(customIconFolder, "mouse_middle.png"); Orig.BLeftDouble = LoadRessource(customIconFolder, "mouse_left_double.png"); Orig.BRightDouble = LoadRessource(customIconFolder, "mouse_right_double.png"); Orig.BWheelUp = LoadRessource(customIconFolder, "mouse_wheel_up.png"); Orig.BWheelDown = LoadRessource(customIconFolder, "mouse_wheel_down.png"); Orig.MCtrl = LoadRessource(customIconFolder, "mouse_modifier_ctrl.png"); Orig.MWin = LoadRessource(customIconFolder, "mouse_modifier_win.png"); Orig.MAlt = LoadRessource(customIconFolder, "mouse_modifier_alt.png"); Orig.MShift = LoadRessource(customIconFolder, "mouse_modifier_shift.png"); RefreshScalingCache(); } private static Bitmap LoadRessource(string customIconFolder, string png_name) { if (customIconFolder != null) { try { string customIconPath = Path.Combine(customIconFolder, png_name); if (File.Exists(customIconPath)) { using (var fs = new FileStream(customIconPath, FileMode.Open, FileAccess.Read)) { return new Bitmap(fs); } } } catch (Exception) { } } return new Bitmap(_assembly.GetManifestResourceStream($"KeyNStroke.Resources.{png_name}")); } static double appliedScalingFactor = -1.0; public static void ApplyScalingFactor(double scalingfactor) { scalingfactor = Math.Min(2f, Math.Max(0.1f, scalingfactor)); if (appliedScalingFactor != scalingfactor) { appliedScalingFactor = scalingfactor; RefreshScalingCache(); } } private static void RefreshScalingCache() { if (appliedScalingFactor == -1.0) return; var newByDpi = new Dictionary(); List dpis = NativeMethodsWindow.GetAllUsedDpis(); foreach (uint dpi in dpis) { if (!newByDpi.ContainsKey(dpi)) { newByDpi.Add(dpi, CreateScaledBitmapCollection((float)appliedScalingFactor, dpi)); } } ScaledByDpi = newByDpi; lastComposedBitmap = null; // Force regeneration of Image } private static BitmapCollection CreateScaledBitmapCollection(float scalingFactor, uint dpi) { BitmapCollection scaled = new BitmapCollection { BMouse = Scale(scalingFactor, Orig.BMouse, dpi), BLeft = Scale(scalingFactor, Orig.BLeft, dpi), BRight = Scale(scalingFactor, Orig.BRight, dpi), BMiddle = Scale(scalingFactor, Orig.BMiddle, dpi), BLeftDouble = Scale(scalingFactor, Orig.BLeftDouble, dpi), BRightDouble = Scale(scalingFactor, Orig.BRightDouble, dpi), BWheelUp = Scale(scalingFactor, Orig.BWheelUp, dpi), BWheelDown = Scale(scalingFactor, Orig.BWheelDown, dpi), MCtrl = Scale(scalingFactor, Orig.MCtrl, dpi), MWin = Scale(scalingFactor, Orig.MWin, dpi), MAlt = Scale(scalingFactor, Orig.MAlt, dpi), MShift = Scale(scalingFactor, Orig.MShift, dpi) }; return scaled; } private static Bitmap Scale(float scalingFactor, Bitmap original, uint dpi) { var dpiScale = (float)dpi / 96.0f; scalingFactor *= dpiScale; var scaledWidth = (int)(original.Width * scalingFactor); Log.e("DPI", $"Scale: DPI: {dpi}, dpiScaleFactor:{dpiScale}, scaledWidth:{scaledWidth}, totalScalingFactor:{scalingFactor}"); var scaledHeight = (int)(original.Height * scalingFactor); var scaledBitmap = new Bitmap(scaledWidth, scaledHeight, PixelFormat.Format32bppArgb); // Draw original image onto new bitmap and interpolate Graphics graph = Graphics.FromImage(scaledBitmap); graph.InterpolationMode = InterpolationMode.High; graph.CompositingQuality = CompositingQuality.HighQuality; graph.SmoothingMode = SmoothingMode.AntiAlias; graph.DrawImage(original, new Rectangle(0, 0, scaledWidth, scaledHeight)); return scaledBitmap; } public struct ComposeOptions { public uint dpi; public bool addBMouse; public bool addBLeft; public bool addBRight; public bool addBMiddle; public bool addBLeftDouble; public bool addBRightDouble; public bool addBWheelUp; public bool addBWheelDown; public bool addMCtrl; public bool addMAlt; public bool addMWin; public bool addMShift; public static bool operator ==(ComposeOptions first, ComposeOptions second) { return Equals(first, second); } public static bool operator !=(ComposeOptions first, ComposeOptions second) { return !(first == second); } public override string ToString() { String s = ""; if (addBMouse) s += "Mouse "; if (addBLeft) s += "Left "; if (addBRight) s += "Right "; if (addBMiddle) s += "Middle "; if (addBLeftDouble) s += "LeftDouble "; if (addBRightDouble) s += "RightDouble "; if (addBWheelUp) s += "WheelUp "; if (addBWheelDown) s += "WheelDown "; if (addMCtrl) s += "Ctrl "; if (addMAlt) s += "Alt "; if (addMWin) s += "Win "; if (addMShift) s += "Shift "; return s; } public override bool Equals(object obj) { return base.Equals(obj); } public override int GetHashCode() { return base.GetHashCode(); } } static ComposeOptions lastComposeOptions; static Bitmap lastComposedBitmap; public static Bitmap Compose(ComposeOptions c) { if (lastComposedBitmap != null && (c == lastComposeOptions)) { return lastComposedBitmap; } var byDpi = ScaledByDpi; // take reference to prevent datarace with update/replace logic on scaling factor change if (!byDpi.ContainsKey(c.dpi)) { byDpi.Add(c.dpi, CreateScaledBitmapCollection((float)appliedScalingFactor, c.dpi)); } BitmapCollection scaled = byDpi[c.dpi]; Log.e("DPI", $"COMPOSE! {c.dpi}, {c}, {scaled.BMouse.Size.Width}"); var targetBitmap = new Bitmap(scaled.BMouse.Size.Width, scaled.BMouse.Size.Height, PixelFormat.Format32bppArgb); Graphics graph = Graphics.FromImage(targetBitmap); if (c.addBMouse) graph.DrawImageUnscaled(scaled.BMouse, 0, 0); if (c.addBLeft) graph.DrawImageUnscaled(scaled.BLeft, 0, 0); if (c.addBRight) graph.DrawImageUnscaled(scaled.BRight, 0, 0); if (c.addBMiddle) graph.DrawImageUnscaled(scaled.BMiddle, 0, 0); if (c.addBLeftDouble) graph.DrawImageUnscaled(scaled.BLeftDouble, 0, 0); if (c.addBRightDouble) graph.DrawImageUnscaled(scaled.BRightDouble, 0, 0); if (c.addBWheelUp) graph.DrawImageUnscaled(scaled.BWheelUp, 0, 0); if (c.addBWheelDown) graph.DrawImageUnscaled(scaled.BWheelDown, 0, 0); if (c.addMCtrl) graph.DrawImageUnscaled(scaled.MCtrl, 0, 0); if (c.addMWin) graph.DrawImageUnscaled(scaled.MWin, 0, 0); if (c.addMAlt) graph.DrawImageUnscaled(scaled.MAlt, 0, 0); if (c.addMShift) graph.DrawImageUnscaled(scaled.MShift, 0, 0); lastComposeOptions = c; lastComposedBitmap = targetBitmap; return targetBitmap; } } } ================================================ FILE: KeyNStroke/KeyNStroke.csproj ================================================  Debug AnyCPU {BA117557-FEBC-45C5-8895-4017D05C3DCD} WinExe KeyNStroke Key-n-Stroke v4.8 512 {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 4 true true false publish\ true Disk false Foreground 7 Days false false true 0 1.2.0.%2a false true AnyCPU true full false bin\Debug\ DEBUG;TRACE prompt 4 AnyCPU pdbonly true bin\Release\ TRACE prompt 4 KeyNStroke.App app.manifest ..\packages\Costura.Fody.5.0.2\lib\netstandard1.0\Costura.dll ..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll True True ..\packages\System.AppContext.4.3.0\lib\net463\System.AppContext.dll True True ..\packages\System.Console.4.3.0\lib\net46\System.Console.dll True True ..\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll ..\packages\System.Diagnostics.Tracing.4.3.0\lib\net462\System.Diagnostics.Tracing.dll True True ..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll True True ..\packages\System.IO.4.3.0\lib\net462\System.IO.dll True True ..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll True True ..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll True True ..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll True True ..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll True True ..\packages\System.Linq.4.3.0\lib\net463\System.Linq.dll True True ..\packages\System.Linq.Expressions.4.3.0\lib\net463\System.Linq.Expressions.dll True True ..\packages\System.Net.Http.4.3.0\lib\net46\System.Net.Http.dll True True ..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll True True ..\packages\System.Reflection.4.3.0\lib\net462\System.Reflection.dll True True ..\packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll True True ..\packages\System.Runtime.Extensions.4.3.0\lib\net462\System.Runtime.Extensions.dll True True ..\packages\System.Runtime.InteropServices.4.3.0\lib\net463\System.Runtime.InteropServices.dll True True ..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll True True ..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net463\System.Security.Cryptography.Algorithms.dll True True ..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll True True ..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll True True ..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll True True ..\packages\System.Text.RegularExpressions.4.3.0\lib\net463\System.Text.RegularExpressions.dll True True 4.0 ..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll True True ..\packages\WpfColorFontDialog.1.0.7\lib\net40\WpfColorFontDialog.dll False ..\packages\Extended.Wpf.Toolkit.3.8.1\lib\net40\Xceed.Wpf.Toolkit.dll False AnnotateLine.xaml CursorIndicator1.xaml ReadShortcut.xaml ResizeButton.xaml Welcome.xaml Designer MSBuild:Compile MSBuild:Compile Designer App.xaml Code Form ButtonIndicator1.cs KeystrokeDisplay.xaml LabeledSlider.xaml Settings1.xaml Component Designer MSBuild:Compile Designer MSBuild:Compile MSBuild:Compile Designer Designer MSBuild:Compile Designer MSBuild:Compile Code True True Resources.resx True Settings.settings True ButtonIndicator1.cs ResXFileCodeGenerator Resources.Designer.cs SettingsSingleFileGenerator Settings.Designer.cs Designer MSBuild:Compile Designer MSBuild:Compile Designer MSBuild:Compile False Microsoft .NET Framework 4.8 %28x86 and x64%29 true False .NET Framework 3.5 SP1 false 5.0.2 runtime; build; native; contentfiles; analyzers; buildtransitive all 6.3.0 runtime; build; native; contentfiles; analyzers; buildtransitive all Dieses Projekt verweist auf mindestens ein NuGet-Paket, das auf diesem Computer fehlt. Verwenden Sie die Wiederherstellung von NuGet-Paketen, um die fehlenden Dateien herunterzuladen. Weitere Informationen finden Sie unter "http://go.microsoft.com/fwlink/?LinkID=322105". Die fehlende Datei ist "{0}". ================================================ FILE: KeyNStroke/KeyboardHook.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Diagnostics; using System.Runtime.InteropServices; using System.Windows.Input; using KeyNStroke; namespace KeyNStroke { /// Some Parts are from /// KEYBOARD.CS (c) 2006 by Emma Burrows /// /// Low-level keyboard intercept class /// public class KeyboardHook : IDisposable, IKeyboardRawEventProvider { #region Initializion /// /// Sets up a keyboard hook /// public KeyboardHook() { RegisterKeyboardHook(); } #endregion #region Hook Add/Remove // Keyboard Hook Identifier for WinApi private const int WH_KEYBOARD_LL = 13; //Variables used in the call to SetWindowsHookEx private NativeMethodsKeyboard.HookHandlerDelegate proc; private IntPtr hookID = IntPtr.Zero; /// /// Registers the function HookCallback for the global key events winapi /// private void RegisterKeyboardHook() { if (hookID != IntPtr.Zero) return; proc = new NativeMethodsKeyboard.HookHandlerDelegate(HookCallback); using (Process curProcess = Process.GetCurrentProcess()) { using (ProcessModule curModule = curProcess.MainModule) { hookID = NativeMethodsKeyboard.SetWindowsHookEx(WH_KEYBOARD_LL, proc, NativeMethodsKeyboard.GetModuleHandle(curModule.ModuleName), 0); } } } /// /// Unregisters the winapi hook for global key events /// private void UnregisterKeyboardHook() { if (hookID == IntPtr.Zero) return; NativeMethodsKeyboard.UnhookWindowsHookEx(hookID); hookID = IntPtr.Zero; } #endregion #region EventHandling //Keyboard API constants: press or release private const int WM_KEYUP = 0x0101; private const int WM_SYSKEYUP = 0x0105; private const int WM_KEYDOWN = 0x0100; private const int WM_SYSKEYDOWN = 0x0104; private const int VK_SHIFT = 0x10, VK_CONTROL = 0x11, VK_MENU = 0x12, VK_CAPITAL = 0x14, VK_LWIN = 0x5B, VK_RWIN = 0x5C, VK_NUMLOCK = 0x90, VK_SCROLL = 0x91, VK_LSHIFT = 0xA0, VK_RSHIFT = 0xA1, VK_LCONTROL = 0xA2, VK_RCONTROL = 0xA3, VK_LMENU = 0xA4, VK_RMENU = 0xA5; /// /// Processes the key event captured by the hook. /// private IntPtr HookCallback(int nCode, IntPtr wParam, ref NativeMethodsKeyboard.KBDLLHOOKSTRUCT lParam) { if (nCode >= 0) { KeyboardRawEventArgs e = new KeyboardRawEventArgs(lParam); e.keyState = new byte[256]; //NativeMethodsKeyboard.GetKeyboardState(e.keyState); probability of dataraces if (wParam == (IntPtr)WM_KEYDOWN || wParam == (IntPtr)WM_SYSKEYDOWN) { e.Method = KeyUpDown.Down; } else if (wParam == (IntPtr)WM_KEYUP || wParam == (IntPtr)WM_SYSKEYUP) { e.Method = KeyUpDown.Up; } if (e.Method != KeyUpDown.Undefined) { CheckModifiers(e); FixKeyStateArray(e); Log.e("KE", $"EVENT scanCode={lParam.scanCode} vkCode={e.vkCode} method={e.Method} uppercase={e.Uppercase} "); OnKeyEvent(e); } /*System.Diagnostics.Debug.WriteLine( String.Format("Key: sc {0} vk {1} ext {2} fl {3}, {4}", lParam.scanCode, lParam.vkCode, lParam.dwExtraInfo, lParam.flags, e.Method)); */ if (e.preventDefault) { return IntPtr.Add(IntPtr.Zero, 1); } else { return NativeMethodsKeyboard.CallNextHookEx(hookID, nCode, wParam, ref lParam); } } else { //Pass key to next application return NativeMethodsKeyboard.CallNextHookEx(hookID, nCode, wParam, ref lParam); } } /// /// Checks whether Alt, Shift, Control or CapsLock /// is enabled at the same time as another key. /// Modify the relevant sections and return type /// depending on what you want to do with modifier keys. /// private void CheckModifiers(KeyboardRawEventArgs e) { e.Shift = CheckModifierDown(VK_SHIFT); e.Ctrl = CheckModifierDown(VK_CONTROL); e.Alt = CheckModifierDown(VK_MENU); e.Caps = CheckModifierToggled(VK_CAPITAL); e.LWin = CheckModifierDown(VK_LWIN); e.RWin = CheckModifierDown(VK_RWIN); e.Numlock = CheckModifierToggled(VK_NUMLOCK); e.Scrollock = CheckModifierToggled(VK_SCROLL); e.LShift = CheckModifierDown(VK_LSHIFT); e.RShift = CheckModifierDown(VK_RSHIFT); e.LCtrl = CheckModifierDown(VK_LCONTROL); e.RCtrl = CheckModifierDown(VK_RCONTROL); e.LAlt = CheckModifierDown(VK_LMENU); e.RAlt = CheckModifierDown(VK_RMENU); } /// /// Uses the winapi to check if the VK key modifiercode is pressed /// /// /// private bool CheckModifierDown(int modifiercode) { // SHORT NativeMethodsKeyboard.GetKeyState(int) // The return value specifies the status of the specified virtual key, as follows: // - If the high-order bit is 1, the key is down; otherwise, it is up. // - If the low-order bit is 1, the key is toggled. A key, such as // the CAPS LOCK key, is toggled if it is turned on. The key is // off and untoggled if the low-order bit is 0. A toggle key's // indicator light (if any) on the keyboard will be on when the // key is toggled, and off when the key is untoggled. return ((NativeMethodsKeyboard.GetKeyState(modifiercode) & 0x8000) != 0); } /// /// Uses the winapi to check weather the caps/numlock/scrolllock is activated /// /// /// private bool CheckModifierToggled(int modifiercode) { return (NativeMethodsKeyboard.GetKeyState(modifiercode) & 0x0001) != 0; } private void FixKeyStateArray(KeyboardRawEventArgs e) { Log.e("KP", "FixKeyStateArray()"); if (e.Uppercase) { e.keyState[VK_SHIFT] = 129; } else { e.keyState[VK_SHIFT] = 0; } //return; #pragma warning disable CS0162 // Unerreichbarer Code wurde entdeckt. e.keyState[VK_CONTROL] = (byte)(e.Ctrl ? 129 : 0); #pragma warning restore CS0162 // Unerreichbarer Code wurde entdeckt. e.keyState[VK_MENU] = (byte)(e.Alt ? 129 : 0); e.keyState[VK_CAPITAL] = (byte)(e.Caps ? 129 : 0); e.keyState[VK_LWIN] = (byte)(e.LWin ? 129 : 0); e.keyState[VK_RWIN] = (byte)(e.RWin ? 129 : 0); e.keyState[VK_NUMLOCK] = (byte)(e.Numlock ? 129 : 0); e.keyState[VK_SCROLL] = (byte)(e.Scrollock ? 129 : 0); e.keyState[VK_LSHIFT] = (byte)(e.LShift ? 129 : 0); e.keyState[VK_RSHIFT] = (byte)(e.RShift ? 129 : 0); e.keyState[VK_LCONTROL] = (byte)(e.LCtrl ? 129 : 0); e.keyState[VK_RCONTROL] = (byte)(e.RCtrl ? 129 : 0); e.keyState[VK_LMENU] = (byte)(e.LAlt ? 129 : 0); e.keyState[VK_RMENU] = (byte)(e.RAlt ? 129 : 0); } #endregion #region Event Forwarding /// /// Fires if key is pressed or released. /// public event KeyboardRawEventHandler KeyEvent; /// /// Raises the KeyEvent event. /// /// An instance of KeyboardRawEvent public void OnKeyEvent(KeyboardRawEventArgs e) { if (KeyEvent != null) KeyEvent(e); } #endregion #region Finalizing and Disposing ~KeyboardHook() { Log.e("HOOK", "~KeyboardHook"); UnregisterKeyboardHook(); } /// /// Releases the keyboard hook. /// public void Dispose() { UnregisterKeyboardHook(); } #endregion } } ================================================ FILE: KeyNStroke/KeyboardLayoutParser.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using KeyNStroke; namespace KeyNStroke { public class KeyboardLayoutParser { public static string Parse(KeyboardRawEventArgs e) { StringBuilder sb = new StringBuilder(128); int lParam = 0; // Bits in lParam // 16-23 Scan code. // 24 Extended-key flag. Distinguishes some keys on an enhanced keyboard. // 25 "Do not care" bit. The application calling this function sets this // bit to indicate that the function should not distinguish between left // and right CTRL and SHIFT keys, for example. lParam = e.Kbdllhookstruct.scanCode << 16; int result = NativeMethodsKeyboard.GetKeyNameText(lParam, sb, 128); return sb.ToString(); } public static string ParseViaMapKeycode(KeyboardRawEventArgs e) { uint r = NativeMethodsKeyboard.MapVirtualKey((uint)e.vkCode, NativeMethodsKeyboard.MAPVK_VK_TO_CHAR); return ((char)r).ToString(); } public static string ParseViaToAscii(KeyboardRawEventArgs e) { byte[] inBuffer = new byte[2]; int buffertype = NativeMethodsKeyboard.ToAscii(e.vkCode, e.Kbdllhookstruct.scanCode, e.keyState, inBuffer, e.Alt ? 1 : 0); if (buffertype < 0) // deadkey { } else if (buffertype == 1) // one char in inBuffer[0] { char key = (char)inBuffer[0]; return key.ToString(); } else if (buffertype == 2) // two chars in inBuffer { char key = (char)inBuffer[0]; char key2 = (char)inBuffer[1]; return key.ToString() + key2.ToString(); } else if (buffertype == 0) { // no translation } return ""; } public static string ParseViaToUnicode(KeyboardRawEventArgs e) { StringBuilder inBuffer = new StringBuilder(128); int buffertype = NativeMethodsKeyboard.ToUnicode(e.vkCode, e.Kbdllhookstruct.scanCode, e.keyState, inBuffer, 128, 0); /* 4 == "don't change keyboard state" (Windows 10 version 1607 and higher) */ Log.e("KP", String.Format(" ParseViaToUnicode(): First call to ToUnicode: returned={0} translated='{1}' alt={2} ctrl={3} vk={4}", buffertype, inBuffer.ToString(), e.Alt, e.Ctrl, e.vkCode)); string keystate = ""; for (int i = 0; i < e.keyState.Length; i++ ) { if(e.keyState[i] != 0) { keystate += " " + ((WindowsVirtualKey) i).ToString() + ":" + e.keyState[i]; } } Log.e("KP", " ParseViaToUnicode(): Key state: " + keystate); // call ToUnicode again, otherwise it will destoy the dead key for the rest of the system int buffertype2 = NativeMethodsKeyboard.ToUnicode(e.vkCode, e.Kbdllhookstruct.scanCode, e.keyState, inBuffer, 128, 0); /* 4 == "don't change keyboard state" (Windows 10 version 1607 and higher) */ Log.e("KP", String.Format(" ParseViaToUnicode(): Secnd call to ToUnicode: returned={0} translated='{1}' alt={2} vk={3}", buffertype2, inBuffer.ToString(), e.Alt, e.vkCode)); if (buffertype < 0) // deadkey { // return DEADKEY, so the next key can try to assemble the deadkey //return "DEADKEY"; return buffertype2 >= 1 ? inBuffer.ToString(0, 1) : ""; } else if(buffertype2 < 0) // type two dead keys in a row { Log.e("KP", " ParseViaToUnicode(): TwoDeadKeysInARow " + buffertype2.ToString() + " & deadkey"); return buffertype >= 1 ? inBuffer.ToString(0, 1) : ""; } else if (buffertype2 >= 1) // buffertype chars in inBuffer[0..buffertype] { string out_ = inBuffer.ToString(0, buffertype2); if (out_ == "\b") // Backspace is no text { return ""; } return out_; } else if (buffertype2 == 0) { // no translation } return ""; } public static string ProcessDeadkeyWithNextKey(KeyboardRawEventArgs dead, KeyboardRawEventArgs e) { Log.e("KP", " ProcessDeadkeyWithNextKey()"); StringBuilder inBuffer = new StringBuilder(128); int buffertype = NativeMethodsKeyboard.ToUnicode(dead.vkCode, dead.Kbdllhookstruct.scanCode, dead.keyState, inBuffer, 128, 4); /* 4 == "don't change keyboard state" (Windows 10 version 1607 and higher) */ Log.e("KP", String.Format(" ProcessDeadkeyWithNextKey(): First call to ToUnicode: returned={0} translated='{1}' alt={2} vk={3}", buffertype, inBuffer.ToString(), e.Alt, e.vkCode)); buffertype = NativeMethodsKeyboard.ToUnicode(e.vkCode, e.Kbdllhookstruct.scanCode, e.keyState, inBuffer, 128, 4); /* 4 == "don't change keyboard state" (Windows 10 version 1607 and higher) */ Log.e("KP", String.Format(" ProcessDeadkeyWithNextKey(): Sednd call to ToUnicode: returned={0} translated='{1}' alt={2} vk={3}", buffertype, inBuffer.ToString(), e.Alt, e.vkCode)); if (buffertype >= 1) // buffertype chars in inBuffer[0..buffertype] { return inBuffer.ToString(0, buffertype); } else if (buffertype == 0) { // no translation } return ""; } /* int convertVirtualKeyToWChar(int virtualKey, PWCHAR outputChar, PWCHAR deadChar) { int i = 0; short state = 0; int capsLock; int shift = -1; int mod = 0; int charCount = 0; WCHAR baseChar; WCHAR diacritic; *outputChar = 0; capsLock = (GetKeyState(VK_CAPITAL) & 0x1); do { state = GetAsyncKeyState(pgCharModifiers->pVkToBit[i].Vk); if (pgCharModifiers->pVkToBit[i].Vk == VK_SHIFT) shift = i + 1; // Get modification number for Shift key if (state & ~SHRT_MAX) { if (mod == 0) mod = i + 1; else mod = 0; // Two modifiers at the same time! } i++; } while (pgCharModifiers->pVkToBit[i].Vk != 0); SEARCH_VK_IN_CONVERSION_TABLE(1) SEARCH_VK_IN_CONVERSION_TABLE(2) SEARCH_VK_IN_CONVERSION_TABLE(3) SEARCH_VK_IN_CONVERSION_TABLE(4) SEARCH_VK_IN_CONVERSION_TABLE(5) SEARCH_VK_IN_CONVERSION_TABLE(6) SEARCH_VK_IN_CONVERSION_TABLE(7) SEARCH_VK_IN_CONVERSION_TABLE(8) SEARCH_VK_IN_CONVERSION_TABLE(9) SEARCH_VK_IN_CONVERSION_TABLE(10) if (*deadChar != 0) // I see dead characters... { i = 0; do { baseChar = (WCHAR) pgDeadKey[i].dwBoth; diacritic = (WCHAR) (pgDeadKey[i].dwBoth >> 16); if ((baseChar == *outputChar) && (diacritic == *deadChar)) { *deadChar = 0; *outputChar = (WCHAR) pgDeadKey[i].wchComposed; } i++; } while (pgDeadKey[i].dwBoth != 0); } return charCount; }*/ } } ================================================ FILE: KeyNStroke/KeyboardRawEvent.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Input; using KeyNStroke; namespace KeyNStroke { public enum KeyUpDown { Undefined, Up, Down } public class KeyboardRawEventArgs { public bool Shift; public bool LShift; public bool RShift; public bool Ctrl; public bool LCtrl; public bool RCtrl; public bool Caps; public bool LWin; public bool RWin; public bool Alt; public bool LAlt; public bool RAlt; // Alt Gr public bool Numlock; public bool Scrollock; public int vkCode { get { return Kbdllhookstruct.vkCode; } } private Key key; public Key Key { get { return key; } } public KeyUpDown Method = KeyUpDown.Undefined; public bool Uppercase { get { return (Shift && !Caps) || (Caps && !Shift); } } public bool OnlyShiftOrCaps { get { return !Ctrl && !LWin && !RWin && !Alt; } } public bool NoModifiers { get { return !Ctrl && !LWin && !RWin && !Alt && !Shift; } } public bool Win { get { return LWin || RWin; } } public NativeMethodsKeyboard.KBDLLHOOKSTRUCT Kbdllhookstruct; public byte[] keyState; // 256 bytes public bool preventDefault = false; public KeyboardRawEventArgs(NativeMethodsKeyboard.KBDLLHOOKSTRUCT Kbdllhookstruct) { this.Kbdllhookstruct = Kbdllhookstruct; this.key = KeyInterop.KeyFromVirtualKey(this.vkCode); /* cache */ } } public delegate void KeyboardRawEventHandler(KeyboardRawEventArgs e); public interface IKeyboardRawEventProvider : IDisposable { event KeyboardRawEventHandler KeyEvent; } public enum WindowsVirtualKey { [Description("Left mouse button")] VK_LBUTTON = 0x01, [Description("Right mouse button")] VK_RBUTTON = 0x02, [Description("Control-break processing")] VK_CANCEL = 0x03, [Description("Middle mouse button (three-button mouse)")] VK_MBUTTON = 0x04, [Description("X1 mouse button")] VK_XBUTTON1 = 0x05, [Description("X2 mouse button")] VK_XBUTTON2 = 0x06, [Description("BACKSPACE key")] VK_BACK = 0x08, [Description("TAB key")] VK_TAB = 0x09, [Description("CLEAR key")] VK_CLEAR = 0x0C, [Description("ENTER key")] VK_RETURN = 0x0D, [Description("SHIFT key")] VK_SHIFT = 0x10, [Description("CTRL key")] VK_CONTROL = 0x11, [Description("ALT key")] VK_MENU = 0x12, [Description("PAUSE key")] VK_PAUSE = 0x13, [Description("CAPS LOCK key")] VK_CAPITAL = 0x14, [Description("IME Kana mode")] VK_KANA = 0x15, [Description("IME Hanguel mode (maintained for compatibility; use VK_HANGUL)")] VK_HANGUEL = 0x15, [Description("IME Hangul mode")] VK_HANGUL = 0x15, [Description("IME Junja mode")] VK_JUNJA = 0x17, [Description("IME final mode")] VK_FINAL = 0x18, [Description("IME Hanja mode")] VK_HANJA = 0x19, [Description("IME Kanji mode")] VK_KANJI = 0x19, [Description("ESC key")] VK_ESCAPE = 0x1B, [Description("IME convert")] VK_CONVERT = 0x1C, [Description("IME nonconvert")] VK_NONCONVERT = 0x1D, [Description("IME accept")] VK_ACCEPT = 0x1E, [Description("IME mode change request")] VK_MODECHANGE = 0x1F, [Description("SPACEBAR")] VK_SPACE = 0x20, [Description("PAGE UP key")] VK_PRIOR = 0x21, [Description("PAGE DOWN key")] VK_NEXT = 0x22, [Description("END key")] VK_END = 0x23, [Description("HOME key")] VK_HOME = 0x24, [Description("LEFT ARROW key")] VK_LEFT = 0x25, [Description("UP ARROW key")] VK_UP = 0x26, [Description("RIGHT ARROW key")] VK_RIGHT = 0x27, [Description("DOWN ARROW key")] VK_DOWN = 0x28, [Description("SELECT key")] VK_SELECT = 0x29, [Description("PRINT key")] VK_PRINT = 0x2A, [Description("EXECUTE key")] VK_EXECUTE = 0x2B, [Description("PRINT SCREEN key")] VK_SNAPSHOT = 0x2C, [Description("INS key")] VK_INSERT = 0x2D, [Description("DEL key")] VK_DELETE = 0x2E, [Description("HELP key")] VK_HELP = 0x2F, [Description("0 key")] K_0 = 0x30, [Description("1 key")] K_1 = 0x31, [Description("2 key")] K_2 = 0x32, [Description("3 key")] K_3 = 0x33, [Description("4 key")] K_4 = 0x34, [Description("5 key")] K_5 = 0x35, [Description("6 key")] K_6 = 0x36, [Description("7 key")] K_7 = 0x37, [Description("8 key")] K_8 = 0x38, [Description("9 key")] K_9 = 0x39, [Description("A key")] K_A = 0x41, [Description("B key")] K_B = 0x42, [Description("C key")] K_C = 0x43, [Description("D key")] K_D = 0x44, [Description("E key")] K_E = 0x45, [Description("F key")] K_F = 0x46, [Description("G key")] K_G = 0x47, [Description("H key")] K_H = 0x48, [Description("I key")] K_I = 0x49, [Description("J key")] K_J = 0x4A, [Description("K key")] K_K = 0x4B, [Description("L key")] K_L = 0x4C, [Description("M key")] K_M = 0x4D, [Description("N key")] K_N = 0x4E, [Description("O key")] K_O = 0x4F, [Description("P key")] K_P = 0x50, [Description("Q key")] K_Q = 0x51, [Description("R key")] K_R = 0x52, [Description("S key")] K_S = 0x53, [Description("T key")] K_T = 0x54, [Description("U key")] K_U = 0x55, [Description("V key")] K_V = 0x56, [Description("W key")] K_W = 0x57, [Description("X key")] K_X = 0x58, [Description("Y key")] K_Y = 0x59, [Description("Z key")] K_Z = 0x5A, [Description("Left Windows key (Natural keyboard)")] VK_LWIN = 0x5B, [Description("Right Windows key (Natural keyboard)")] VK_RWIN = 0x5C, [Description("Applications key (Natural keyboard)")] VK_APPS = 0x5D, [Description("Computer Sleep key")] VK_SLEEP = 0x5F, [Description("Numeric keypad 0 key")] VK_NUMPAD0 = 0x60, [Description("Numeric keypad 1 key")] VK_NUMPAD1 = 0x61, [Description("Numeric keypad 2 key")] VK_NUMPAD2 = 0x62, [Description("Numeric keypad 3 key")] VK_NUMPAD3 = 0x63, [Description("Numeric keypad 4 key")] VK_NUMPAD4 = 0x64, [Description("Numeric keypad 5 key")] VK_NUMPAD5 = 0x65, [Description("Numeric keypad 6 key")] VK_NUMPAD6 = 0x66, [Description("Numeric keypad 7 key")] VK_NUMPAD7 = 0x67, [Description("Numeric keypad 8 key")] VK_NUMPAD8 = 0x68, [Description("Numeric keypad 9 key")] VK_NUMPAD9 = 0x69, [Description("Multiply key")] VK_MULTIPLY = 0x6A, [Description("Add key")] VK_ADD = 0x6B, [Description("Separator key")] VK_SEPARATOR = 0x6C, [Description("Subtract key")] VK_SUBTRACT = 0x6D, [Description("Decimal key")] VK_DECIMAL = 0x6E, [Description("Divide key")] VK_DIVIDE = 0x6F, [Description("F1 key")] VK_F1 = 0x70, [Description("F2 key")] VK_F2 = 0x71, [Description("F3 key")] VK_F3 = 0x72, [Description("F4 key")] VK_F4 = 0x73, [Description("F5 key")] VK_F5 = 0x74, [Description("F6 key")] VK_F6 = 0x75, [Description("F7 key")] VK_F7 = 0x76, [Description("F8 key")] VK_F8 = 0x77, [Description("F9 key")] VK_F9 = 0x78, [Description("F10 key")] VK_F10 = 0x79, [Description("F11 key")] VK_F11 = 0x7A, [Description("F12 key")] VK_F12 = 0x7B, [Description("F13 key")] VK_F13 = 0x7C, [Description("F14 key")] VK_F14 = 0x7D, [Description("F15 key")] VK_F15 = 0x7E, [Description("F16 key")] VK_F16 = 0x7F, [Description("F17 key")] VK_F17 = 0x80, [Description("F18 key")] VK_F18 = 0x81, [Description("F19 key")] VK_F19 = 0x82, [Description("F20 key")] VK_F20 = 0x83, [Description("F21 key")] VK_F21 = 0x84, [Description("F22 key")] VK_F22 = 0x85, [Description("F23 key")] VK_F23 = 0x86, [Description("F24 key")] VK_F24 = 0x87, [Description("NUM LOCK key")] VK_NUMLOCK = 0x90, [Description("SCROLL LOCK key")] VK_SCROLL = 0x91, [Description("Left SHIFT key")] VK_LSHIFT = 0xA0, [Description("Right SHIFT key")] VK_RSHIFT = 0xA1, [Description("Left CONTROL key")] VK_LCONTROL = 0xA2, [Description("Right CONTROL key")] VK_RCONTROL = 0xA3, [Description("Left MENU key")] VK_LMENU = 0xA4, [Description("Right MENU key")] VK_RMENU = 0xA5, [Description("Browser Back key")] VK_BROWSER_BACK = 0xA6, [Description("Browser Forward key")] VK_BROWSER_FORWARD = 0xA7, [Description("Browser Refresh key")] VK_BROWSER_REFRESH = 0xA8, [Description("Browser Stop key")] VK_BROWSER_STOP = 0xA9, [Description("Browser Search key")] VK_BROWSER_SEARCH = 0xAA, [Description("Browser Favorites key")] VK_BROWSER_FAVORITES = 0xAB, [Description("Browser Start and Home key")] VK_BROWSER_HOME = 0xAC, [Description("Volume Mute key")] VK_VOLUME_MUTE = 0xAD, [Description("Volume Down key")] VK_VOLUME_DOWN = 0xAE, [Description("Volume Up key")] VK_VOLUME_UP = 0xAF, [Description("Next Track key")] VK_MEDIA_NEXT_TRACK = 0xB0, [Description("Previous Track key")] VK_MEDIA_PREV_TRACK = 0xB1, [Description("Stop Media key")] VK_MEDIA_STOP = 0xB2, [Description("Play/Pause Media key")] VK_MEDIA_PLAY_PAUSE = 0xB3, [Description("Start Mail key")] VK_LAUNCH_MAIL = 0xB4, [Description("Select Media key")] VK_LAUNCH_MEDIA_SELECT = 0xB5, [Description("Start Application 1 key")] VK_LAUNCH_APP1 = 0xB6, [Description("Start Application 2 key")] VK_LAUNCH_APP2 = 0xB7, [Description("Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the ';:' key")] VK_OEM_1 = 0xBA, [Description("For any country/region, the '+' key")] VK_OEM_PLUS = 0xBB, [Description("For any country/region, the ',' key")] VK_OEM_COMMA = 0xBC, [Description("For any country/region, the '-' key")] VK_OEM_MINUS = 0xBD, [Description("For any country/region, the '.' key")] VK_OEM_PERIOD = 0xBE, [Description("Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '/?' key")] VK_OEM_2 = 0xBF, [Description("Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '`~' key")] VK_OEM_3 = 0xC0, [Description("Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '[{' key")] VK_OEM_4 = 0xDB, [Description("Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '\\|' key")] VK_OEM_5 = 0xDC, [Description("Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the ']}' key")] VK_OEM_6 = 0xDD, [Description("Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the 'single-quote/double-quote' key")] VK_OEM_7 = 0xDE, [Description("Used for miscellaneous characters; it can vary by keyboard.")] VK_OEM_8 = 0xDF, [Description("Either the angle bracket key or the backslash key on the RT 102-key keyboard")] VK_OEM_102 = 0xE2, [Description("IME PROCESS key")] VK_PROCESSKEY = 0xE5, [Description("Used to pass Unicode characters as if they were keystrokes. The VK_PACKET key is the low word of a 32-bit Virtual Key value used for non-keyboard input methods. For more information, see Remark in KEYBDINPUT, SendInput, WM_KEYDOWN, and WM_KEYUP")] VK_PACKET = 0xE7, [Description("Attn key")] VK_ATTN = 0xF6, [Description("CrSel key")] VK_CRSEL = 0xF7, [Description("ExSel key")] VK_EXSEL = 0xF8, [Description("Erase EOF key")] VK_EREOF = 0xF9, [Description("Play key")] VK_PLAY = 0xFA, [Description("Zoom key")] VK_ZOOM = 0xFB, [Description("PA1 key")] VK_PA1 = 0xFD, [Description("Clear key")] VK_OEM_CLEAR = 0xFE, } } ================================================ FILE: KeyNStroke/KeystrokeDisplay.xaml ================================================  ================================================ FILE: KeyNStroke/KeystrokeDisplay.xaml.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel; using System.Globalization; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Web; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Interop; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Media.Imaging; using System.Windows.Shapes; using System.Windows.Threading; namespace KeyNStroke { /// /// Interaktionslogik für KeystrokeDisplay.xaml /// public partial class KeystrokeDisplay : Window { readonly SettingsStore settings; readonly IKeystrokeEventProvider k; IntPtr windowHandle; Brush OrigInnerPanelBackgroundColor; public KeystrokeDisplay(IKeystrokeEventProvider k, SettingsStore s) { InitializeComponent(); InitializeAnimations(); this.k = k; this.settings = s; this.settings.EnableSettingsMode = false; this.settings.EnablePasswordMode = false; this.settings.PropertyChanged += SettingChanged; this.settings.CallPropertyChangedForAllProperties(); this.buttonResizeWindow.Settings = s; this.buttonResizeInnerPanel.Settings = s; //addWelcomeInfo(); } private void Window_Loaded(object sender, RoutedEventArgs e) { // Window handle is available InitPeriodicTopmostTimer(); windowHandle = new WindowInteropHelper(this).Handle; this.k.KeystrokeEvent += KeystrokeEvent; OrigInnerPanelBackgroundColor = innerPanel.Background; ActivateDisplayOnlyMode(true); DeactivatePasswordProtectionMode(true); if (settings.EnableWindowFade) { FadeOut(); } } #region periodically make TopMost readonly DispatcherTimer makeTopMostTimer = new DispatcherTimer(); void InitPeriodicTopmostTimer() { makeTopMostTimer.Tick += (object sender, EventArgs e) => { IntPtr handle = new WindowInteropHelper(this).Handle; NativeMethodsWindow.SetWindowTopMost(handle); }; makeTopMostTimer.Interval = TimeSpan.FromSeconds(1.0); settings.PropertyChanged += (object sender, PropertyChangedEventArgs e) => { if (e.PropertyName == "PeriodicTopmost") { if (settings.PeriodicTopmost) { makeTopMostTimer.Start(); } else { makeTopMostTimer.Stop(); } } }; } #endregion #region keystroke handler void KeystrokeEvent(KeystrokeEventArgs e) { if (settings == null) return; string pressed = e.ShortcutIdentifier(); e.raw.preventDefault = e.raw.preventDefault || CheckForSettingsMode(pressed); e.raw.preventDefault = e.raw.preventDefault || CheckForPasswordMode(pressed); if (PasswordModeActivated || settings.EnablePasswordMode) { if (e.ShouldBeDisplayed) { if (settings.EnableWindowFade && !SettingsModeActivated) { FadeIn(); } } return; } if (e.ShouldBeDisplayed) { if (settings.EnableWindowFade && !SettingsModeActivated) { FadeIn(); } if (!e.RequiresNewLine && settings.KeystrokeMethod == KeystrokeMethodEnum.TextModeBackspaceCanDeleteText && NumberOfDeletionsAllowed > 0 && LastHistoryLineIsText && !LastHistoryLineRequiredNewLineAfterwards && e.NoModifiers && e.Key == System.Windows.Input.Key.Back) { if (labels.Count > 0) { Log.e("BS", $"delete last char -> {labels[labels.Count - 1].text}"); } Log.e("BS", "NumberOfDeletionsAllowed " + NumberOfDeletionsAllowed.ToString()); if (!RemoveLastChar()) { // again Log.e("BS", " failed"); NumberOfDeletionsAllowed = 0; KeystrokeEvent(e); return; } } else if (e.RequiresNewLine || !settings.KeystrokeMethod.IsTextMode() || !AddingWouldFitInCurrentLine(e.ToString(true, false)) || !LastHistoryLineIsText || LastHistoryLineRequiredNewLineAfterwards) { if (settings.KeystrokeMethod == KeystrokeMethodEnum.ShortcutModeNoText && e.StrokeType == KeystrokeType.Text) { // nothing } else { string e_str = e.ToString(settings.KeystrokeMethod.IsTextMode(), false); AddNextLine(e_str); Log.e("BS", $"new line: {e_str} -> {labels[labels.Count - 1].text}"); NumberOfDeletionsAllowed = e.Deletable ? 1 : 0; Log.e("BS", "NumberOfDeletionsAllowed " + NumberOfDeletionsAllowed.ToString()); } } else { string e_str = e.ToString(settings.KeystrokeMethod.IsTextMode(), false); AddToLine(e_str); Log.e("BS", $"add to line: {e_str} -> {labels[labels.Count-1].text}"); if (e.Deletable) NumberOfDeletionsAllowed += 1; else NumberOfDeletionsAllowed = 0; Log.e("BS", "NumberOfDeletionsAllowed " + NumberOfDeletionsAllowed.ToString()); } LastHistoryLineIsText = e.StrokeType == KeystrokeType.Text; LastHistoryLineRequiredNewLineAfterwards = e.RequiresNewLineAfterwards; } } #endregion #region Opacity Animation DoubleAnimation windowOpacityAnim; Storyboard windowOpacitySB; private void InitializeAnimations() { windowOpacityAnim = new DoubleAnimation { From = 0.0, To = 1.0, Duration = new Duration(TimeSpan.FromMilliseconds(200)), }; windowOpacitySB = new Storyboard(); windowOpacitySB.Children.Add(windowOpacityAnim); Storyboard.SetTarget(windowOpacityAnim, this); Storyboard.SetTargetProperty(windowOpacityAnim, new PropertyPath(Window.OpacityProperty)); } private void FadeOut() { if (!SettingsModeActivated && !PasswordModeActivated) { ToOpacity(0.0, true); } } private void FadeIn() { // Opacity now via background color ToOpacity(1.0, true); } private void FullOpacity() { ToOpacity(1.0, false); } private void ToOpacity(double targetOpacity, bool fade) { if (Math.Abs(Opacity - targetOpacity) > 0.0001) { if (fade) { // Fixme: Animation is restarting for every single keypress on fade-in windowOpacitySB.Stop(); windowOpacityAnim.From = this.Opacity; windowOpacityAnim.To = targetOpacity; Log.e("OPACITY", $"Restart Anim, from {Opacity} to {targetOpacity}."); windowOpacitySB.Begin(this); } else { // https://docs.microsoft.com/de-de/dotnet/framework/wpf/graphics-multimedia/how-to-set-a-property-after-animating-it-with-a-storyboard this.BeginAnimation(Window.OpacityProperty, null); // Break connection between storyboard and property Opacity = targetOpacity; Log.e("OPACITY", $"Remov anim, to {targetOpacity}. {Opacity}"); } } } #endregion #region settingsChanged private void SettingChanged(object sender, PropertyChangedEventArgs e) { if (settings == null) return; switch (e.PropertyName) { case "BackgroundColor": backgroundGrid.Background = new SolidColorBrush(UIHelper.ToMediaColor(settings.BackgroundColor)); break; case "WindowLocation": this.Left = settings.WindowLocation.X; this.Top = settings.WindowLocation.Y; Log.e("KD", String.Format("Apply X: {0}", settings.WindowLocation.X)); break; case "WindowSize": this.Width = settings.WindowSize.Width; this.Height = settings.WindowSize.Height; break; case "PanelLocation": this.innerPanel.Margin = new Thickness(settings.PanelLocation.X, settings.PanelLocation.Y, 0.0, 0.0); break; case "PanelSize": this.innerPanel.Width = settings.PanelSize.Width; this.innerPanel.Height = settings.PanelSize.Height; break; case "LabelFont": case "LabelColor": case "LabelTextAlignment": case "LineDistance": UpdateLabelStyles(); break; case "LabelTextDirection": if (labelStack.VerticalAlignment == VerticalAlignment.Top && settings.LabelTextDirection == TextDirection.Up) { labelStack.VerticalAlignment = VerticalAlignment.Bottom; labelStack.Children.Clear(); for(int i = 0; i < labels.Count; i++) { labelStack.Children.Add(labels[i].label); } UpdateLabelStyles(); } else if (labelStack.VerticalAlignment == VerticalAlignment.Bottom && settings.LabelTextDirection == TextDirection.Down) { labelStack.VerticalAlignment = VerticalAlignment.Top; labelStack.Children.Clear(); for (int i = labels.Count - 1; i >= 0; i--) { labelStack.Children.Add(labels[i].label); } UpdateLabelStyles(); } break; case "HistoryLength": TruncateHistory(); break; case "EnableWindowFade": if (settings.EnableWindowFade && labels.Count == 0) { FadeOut(); } else { FadeIn(); } break; case "KeystrokeHistorySettingsModeShortcut": SetSettingsModeShortcut(settings.KeystrokeHistorySettingsModeShortcut); break; case "EnableSettingsMode": if (settings.EnableSettingsMode) { ActivateSettingsMode(); } else { ActivateDisplayOnlyMode(false); } break; case "KeystrokeHistoryPasswordModeShortcut": SetPasswordModeShortcut(settings.KeystrokeHistoryPasswordModeShortcut); break; case "EnablePasswordMode": if (settings.EnablePasswordMode) { ActivatePasswordProtectionMode(); } else { DeactivatePasswordProtectionMode(false); } break; } } #endregion #region Settings Mode public string SettingsModeShortcut; public void SetSettingsModeShortcut(string shortcut) { if (ValidateShortcutSetting(shortcut)) { SettingsModeShortcut = shortcut; } else { SettingsModeShortcut = settings.KeystrokeHistorySettingsModeShortcutDefault; } } public static bool ValidateShortcutSetting(string shortcut) { if (shortcut == null) return false; if (shortcut.Length == 0) return false; // The last key must not be Ctrl/Alt/Win/Shift, and there must be multiple keys string[] keys = shortcut.Split('+'); // There are spaces around the +, but Split() only accepts chars if (keys.Length < 2) return false; if (keys.Last().Contains("Ctrl")) return false; if (keys.Last().Contains("Alt")) return false; if (keys.Last().Contains("Shift")) return false; if (keys.Last().Contains("Win")) return false; return true; } private bool CheckForSettingsMode(string pressed) { if (SettingsModeShortcut != null && pressed == SettingsModeShortcut) { settings.EnableSettingsMode = !settings.EnableSettingsMode; return true; } return false; } bool SettingsModeActivated = false; void ActivateDisplayOnlyMode(bool force) { if (SettingsModeActivated || force) { FadeIn(); if (!PasswordModeActivated) { NativeMethodsGWL.ClickThrough(this.windowHandle); } NativeMethodsGWL.HideFromAltTab(this.windowHandle); InnerPanelIsDragging = false; buttonClose.Visibility = Visibility.Hidden; buttonResizeInnerPanel.Visibility = Visibility.Hidden; buttonResizeWindow.Visibility = Visibility.Hidden; buttonSettings.Visibility = Visibility.Hidden; buttonLeaveSettingsMode.Visibility = Visibility.Hidden; innerPanel.Background = new SolidColorBrush(Color.FromArgb(0,0,0,0)); backgroundGrid.Background = new SolidColorBrush(UIHelper.ToMediaColor(settings.BackgroundColor)); SettingsModeActivated = false; settings.SaveAll(); } } void ActivateSettingsMode() { if (!SettingsModeActivated) { NativeMethodsGWL.CatchClicks(this.windowHandle); FullOpacity(); InnerPanelIsDragging = false; buttonClose.Visibility = Visibility.Visible; buttonResizeInnerPanel.Visibility = Visibility.Visible; buttonResizeWindow.Visibility = Visibility.Visible; buttonSettings.Visibility = Visibility.Visible; buttonLeaveSettingsMode.Visibility = Visibility.Visible; innerPanel.Background = OrigInnerPanelBackgroundColor; backgroundGrid.Background = new SolidColorBrush(Color.FromArgb(255, 0, 0, 0)); foreach (LabelData d in labels) { d.label.Visibility = Visibility.Hidden; } SettingsModeActivated = true; } } #endregion #region Password Protection Mode public string PasswordModeShortcut; public void SetPasswordModeShortcut(string shortcut) { if (ValidateShortcutSetting(shortcut)) { PasswordModeShortcut = shortcut; } else { PasswordModeShortcut = settings.KeystrokeHistoryPasswordModeShortcutDefault; } PasswordProtectionModeShortcut.Text = PasswordModeShortcut; } private bool CheckForPasswordMode(string pressed) { if (PasswordModeShortcut != null && pressed == PasswordModeShortcut) { settings.EnablePasswordMode = !settings.EnablePasswordMode; return true; } return false; } private void ButtonLeavePasswordMode_Click(object sender, RoutedEventArgs e) { settings.EnablePasswordMode = false; } bool PasswordModeActivated = false; void ActivatePasswordProtectionMode() { if (!PasswordModeActivated) { NativeMethodsGWL.CatchClicks(this.windowHandle); buttonLeavePasswordMode.Visibility = Visibility.Visible; PasswordModeActivated = true; } } void DeactivatePasswordProtectionMode(bool force) { if (PasswordModeActivated || force) { if (!SettingsModeActivated) { NativeMethodsGWL.ClickThrough(this.windowHandle); if (settings.EnableWindowFade && labels.Count == 0) { FadeOut(); } } buttonLeavePasswordMode.Visibility = Visibility.Hidden; PasswordModeActivated = false; } } #endregion #region Dragging of Window and innerPanel private void BackgroundGrid_MouseDown(object sender, MouseButtonEventArgs e) { if (this.SettingsModeActivated) { this.DragMove(); e.Handled = true; } } private void Window_MouseUp(object sender, MouseButtonEventArgs e) { if (this.SettingsModeActivated) { settings.WindowLocation = new Point(this.Left, this.Top); } } private bool InnerPanelIsDragging = false; private Point InnerPanelDragStartCursorPosition; private Point InnerPanelDragStartPosition; private void InnerPanel_MouseDown(object sender, MouseButtonEventArgs e) { InnerPanelDragStartCursorPosition = e.GetPosition(this); InnerPanelIsDragging = true; InnerPanelDragStartPosition = new Point(innerPanel.Margin.Left, innerPanel.Margin.Top); e.Handled = true; } private void InnerPanel_MouseMove(object sender, MouseEventArgs e) { if (InnerPanelIsDragging) { Point current = e.GetPosition(this); Point diff = new Point( current.X - InnerPanelDragStartCursorPosition.X, current.Y - InnerPanelDragStartCursorPosition.Y); innerPanel.Margin = new Thickness(InnerPanelDragStartPosition.X + diff.X, InnerPanelDragStartPosition.Y + diff.Y, 0, 0); } e.Handled = true; } private void InnerPanel_MouseUp(object sender, MouseButtonEventArgs e) { InnerPanelIsDragging = false; settings.PanelLocation = new Point(innerPanel.Margin.Left, innerPanel.Margin.Top); } #endregion #region Button Click and Window Close Events private void ButtonSettings_Click(object sender, RoutedEventArgs e) { ((App)Application.Current).showSettingsWindow(); } private void ButtonClose_Click(object sender, RoutedEventArgs e) { System.Windows.Application.Current.Shutdown(); } private void Window_Closing(object sender, CancelEventArgs e) { } private void ButtonLeaveSettingsMode_Click(object sender, RoutedEventArgs e) { settings.EnableSettingsMode = false; } #endregion #region display and animate Label class LabelData { public Label label; public string text; public Storyboard storyboard; public DispatcherTimer historyTimeout; } readonly List labels = new List(5); bool LastHistoryLineIsText = false; bool LastHistoryLineRequiredNewLineAfterwards = false; int NumberOfDeletionsAllowed = 0; void AddToLine(string chars) { LabelData pack = labels[labels.Count - 1]; if (pack.historyTimeout != null) { pack.historyTimeout.Stop(); if (settings.EnableHistoryTimeout) { pack.historyTimeout.Interval = TimeSpan.FromSeconds(settings.HistoryTimeout); pack.historyTimeout.Start(); } } pack.text += chars; pack.label.Content = pack.text.Replace("_", "__"); //T.Refresh(); } private bool RemoveLastChar() { if (labels.Count == 0) { return false; } LabelData pack = labels[labels.Count - 1]; var content = ((string)pack.label.Content); if (content.Length == 0) return false; pack.text = pack.text.Substring(0, pack.text.Length - 1); pack.label.Content = pack.text.Replace("_", "__"); NumberOfDeletionsAllowed -= 1; return true; } void AddNextLine(string chars) { Label next = new Label { Content = chars.Replace("_", "__") }; ApplyLabelStyle(next); var pack = new LabelData { label = next, text = chars, storyboard = null, historyTimeout = null, }; if (settings.LabelAnimation == KeyNStroke.Style.Slide) { Storyboard showLabelSB = new Storyboard(); var fadeInAnimation = new DoubleAnimation { From = 0, To = 1.0, Duration = new Duration(TimeSpan.FromMilliseconds(200)), }; showLabelSB.Children.Add(fadeInAnimation); Storyboard.SetTarget(fadeInAnimation, next); Storyboard.SetTargetProperty(fadeInAnimation, new PropertyPath(Label.OpacityProperty)); Thickness targetMargin = next.Margin; // from ApplyLabelStyle if (settings.LabelTextDirection == TextDirection.Down) { next.Margin = new Thickness(0, 0, 0, -next.Height); } else { next.Margin = new Thickness(0, -next.Height, 0, 0); } var pushUpwardsAnimation = new ThicknessAnimation { From = next.Margin, To = targetMargin, Duration = new Duration(TimeSpan.FromMilliseconds(200)) }; showLabelSB.Children.Add(pushUpwardsAnimation); Storyboard.SetTarget(pushUpwardsAnimation, next); Storyboard.SetTargetProperty(pushUpwardsAnimation, new PropertyPath(Label.MarginProperty)); pack.storyboard = showLabelSB; pack.storyboard.Begin(pack.label); } if (settings.LabelTextDirection == TextDirection.Up) { labelStack.Children.Add(next); } else { labelStack.Children.Insert(0, next); } if (settings.EnableHistoryTimeout) { pack.historyTimeout = FireOnce(settings.HistoryTimeout, () => { FadeOutLabel(pack); }); } labels.Add(pack); TruncateHistory(); } void TruncateHistory() { while (labels.Count > settings.HistoryLength) { var toRemove = labels[0]; Log.e("LABELREMOVAL", $"Truncate {toRemove.label.Content}. Currently in list: {labels.Count}"); FadeOutLabel(toRemove); labels.Remove(toRemove); } } void FadeOutLabel(LabelData toRemove) { if (toRemove.historyTimeout != null) { toRemove.historyTimeout.Stop(); } if (toRemove.storyboard != null) { toRemove.storyboard.Remove(toRemove.label); } if (settings.LabelAnimation == KeyNStroke.Style.Slide) { Storyboard hideLabelSB = new Storyboard(); var fadeOutAnimation = new DoubleAnimation { From = toRemove.label.Opacity, To = 0, Duration = new Duration(TimeSpan.FromMilliseconds(200)), }; hideLabelSB.Children.Add(fadeOutAnimation); Storyboard.SetTarget(fadeOutAnimation, toRemove.label); Storyboard.SetTargetProperty(fadeOutAnimation, new PropertyPath(Label.OpacityProperty)); fadeOutAnimation.Completed += (object sender, EventArgs e) => { Log.e("LABELREMOVAL", $"{toRemove.label.Content}: Fade out completed"); hideLabelSB.Remove(toRemove.label); labelStack.Children.Remove(toRemove.label); if (settings.EnableWindowFade && labels.Count == 0) { Log.e("LABELREMOVAL", $"{toRemove.label.Content}: Fade out completed -> no more labels -> Window wade out"); FadeOut(); } }; hideLabelSB.Begin(toRemove.label); labels.Remove(toRemove); } else { labelStack.Children.Remove(toRemove.label); labels.Remove(toRemove); if (settings.EnableWindowFade && labels.Count == 0) { Log.e("LABELREMOVAL", $"{toRemove.label.Content}: Fade out completed -> no more labels -> Window wade out"); FadeOut(); } } } /// /// Fires an action once after "seconds" /// public static DispatcherTimer FireOnce(double timeout, Action onElapsed) { var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(timeout) }; var handler = new EventHandler((s, args) => { if (timer.IsEnabled) { onElapsed(); } timer.Stop(); }); timer.Tick += handler; timer.Start(); return timer; } bool AddingWouldFitInCurrentLine(string s) { if (labels.Count == 0) return false; var label = labels[labels.Count - 1].label; var text = (String) label.Content + s; var formattedText = new FormattedText( text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface(label.FontFamily, label.FontStyle, label.FontWeight, label.FontStretch), label.FontSize, Brushes.Black, new NumberSubstitution(), 1); return formattedText.Width < label.ActualWidth - 20; } void ApplyLabelStyle(Label label) { label.Height = 120; label.BeginAnimation(Label.MarginProperty, null); if (settings.LabelTextDirection == TextDirection.Down) { label.Margin = new Thickness(0, 0, 0, -label.Height + settings.LineDistance); label.VerticalContentAlignment = VerticalAlignment.Top; } else { label.Margin = new Thickness(0, -label.Height + settings.LineDistance, 0, 0); label.VerticalContentAlignment = VerticalAlignment.Bottom; } label.BeginAnimation(Label.OpacityProperty, null); label.Opacity = 1.0; label.Foreground = new SolidColorBrush(UIHelper.ToMediaColor(settings.LabelColor)); label.FontSize = settings.LabelFont.Size; label.FontFamily = settings.LabelFont.Family; label.FontStretch = settings.LabelFont.Stretch; label.FontStyle = settings.LabelFont.Style; label.FontWeight = settings.LabelFont.Weight; if (settings.LabelTextAlignment == TextAlignment.Left) { label.HorizontalContentAlignment = HorizontalAlignment.Left; } else if (settings.LabelTextAlignment == TextAlignment.Center) { label.HorizontalContentAlignment = HorizontalAlignment.Center; } else { label.HorizontalContentAlignment = HorizontalAlignment.Right; } } void UpdateLabelStyles() { foreach (var pack in labels) { ApplyLabelStyle(pack.label); } } #endregion } } ================================================ FILE: KeyNStroke/KeystrokeEvent.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Input; namespace KeyNStroke { public enum KeystrokeType { Undefined, Shortcut, Text, Modifiers } public class KeystrokeEventArgs { public string TextModeString; // for use in TextMode public string ShortcutString; // will always contain a shortcut public KeyboardRawEventArgs raw; public bool Shift { get { return raw.Shift; } set { raw.Shift = value; } } public bool LShift { get { return raw.LShift; } set { raw.LShift = value; } } public bool RShift { get { return raw.RShift; } set { raw.RShift = value; } } public bool Ctrl { get { return raw.Ctrl; } set { raw.Ctrl = value; } } public bool LCtrl { get { return raw.LCtrl; } set { raw.LCtrl = value; } } public bool RCtrl { get { return raw.RCtrl; } set { raw.RCtrl = value; } } public bool Caps { get { return raw.Caps; } set { raw.Caps = value; } } public bool LWin { get { return raw.LWin; } set { raw.LWin = value; } } public bool RWin { get { return raw.RWin; } set { raw.RWin = value; } } public bool Alt { get { return raw.Alt; } set { raw.Alt = value; } } public bool LAlt { get { return raw.LAlt; } set { raw.LAlt = value; } } public bool RAlt { get { return raw.RAlt; } set { raw.RAlt = value; } } // Alt Gr public bool Numlock { get { return raw.Numlock; } set { raw.Numlock = value; } } public bool Scrollock { get { return raw.Scrollock; } set { raw.Scrollock = value; } } public Key Key { get { return raw.Key; } } public KeyUpDown Method { get { return raw.Method; } } public bool Uppercase { get { return raw.Uppercase; } } public bool OnlyShiftOrCaps { get { return raw.OnlyShiftOrCaps; } } public bool NoModifiers { get { return raw.NoModifiers; } } public bool Win { get { return raw.Win; } } public bool OrigShift; public bool OrigLShift; public bool OrigRShift; public bool OrigCaps; public bool IsAlpha; public bool IsNumericFromNumpad; public bool IsNumericFromNumbers; public bool IsFunctionKey; public bool IsNoUnicodekey; public bool ModifierToggledEvent; public KeystrokeType StrokeType = KeystrokeType.Undefined; public bool ShouldBeDisplayed; public bool RequiresNewLine; public bool RequiresNewLineAfterwards; public bool Deletable = false; public bool IsNumeric { get { return IsNumericFromNumbers || IsNumericFromNumpad; } } public override string ToString() { return ToString(true); } public string ToString(bool textMode) { if (ShortcutString != null && (StrokeType == KeystrokeType.Shortcut || !textMode)) { return ShortcutString; } else if (StrokeType == KeystrokeType.Text && textMode) { return TextModeString; } return "BUG2"; } public string ToString(bool textMode, bool DoubleAmpersand) { return DoubleAmpersand ? ToString(textMode).Replace("&", "&&") : ToString(textMode); } public string AsShortcutString() { List output = ShortcutModifiersToList(); if (StrokeType == KeystrokeType.Text) { output.Add(TextModeString.ToUpper()); } else { output.Add(TextModeString); } return string.Join(" + ", output); } public List ShortcutModifiersToList() { List Modifiers = new List(); if (OrigShift) Modifiers.Add(SpecialkeysParser.ToString(Key.LeftShift)); if (Ctrl) Modifiers.Add(SpecialkeysParser.ToString(Key.LeftCtrl)); if (Alt) Modifiers.Add(SpecialkeysParser.ToString(Key.LeftAlt)); if (Win) Modifiers.Add(SpecialkeysParser.ToString(Key.LWin)); return Modifiers; } public string ShortcutIdentifier() { if (StrokeType == KeystrokeType.Text) { return null; } else if(StrokeType == KeystrokeType.Shortcut) { List output = new List(); if (this.LCtrl) { output.Add("LeftCtrl"); } if (this.RCtrl) { output.Add("RightCtrl"); } // else if (this.Ctrl) // { // output.Add("Ctrl"); // } if (this.LWin) { output.Add("LeftWin"); } if (this.RWin) { output.Add("RightWin"); } if (this.LAlt) { output.Add("LeftAlt"); } if (this.RAlt) { output.Add("RightAlt"); } // else if (this.Alt) // { // output.Add("Alt"); // } if (this.LShift) { output.Add("LeftShift"); } if (this.RShift) { output.Add("RightShift"); } // else if (this.Shift) // { // output.Add("Shift"); // } string trimmed = TextModeString.Trim(); if (trimmed.Length == 0) // The Space key { output.Add(TextModeString); } else { output.Add(trimmed); } return string.Join(" + ", output); } return null; } public KeystrokeEventArgs(KeyboardRawEventArgs e) { this.raw = e; this.OrigShift = e.Shift; this.OrigCaps = e.Caps; this.OrigLShift = e.LShift; this.OrigRShift = e.RShift; } } public delegate void KeystrokeEventHandler(KeystrokeEventArgs e); public interface IKeystrokeEventProvider { event KeystrokeEventHandler KeystrokeEvent; } } ================================================ FILE: KeyNStroke/KeystrokeParser.cs ================================================ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Input; using KeyNStroke; namespace KeyNStroke { public class KeystrokeParser : IKeystrokeEventProvider { //KeysConverter Converter = new KeysConverter(); #region Constructor public KeystrokeParser(IKeyboardRawEventProvider hook) { hook.KeyEvent += hook_KeyEvent; } #endregion /// /// Gets a KeyboardRawKeyEvent, parses it and forwards it via /// the KeystrokeEvent /// /// void hook_KeyEvent(KeyboardRawEventArgs raw_e) { KeystrokeEventArgs e = new KeystrokeEventArgs(raw_e); e.IsAlpha = CheckIsAlpha(e.raw); e.IsNumericFromNumpad = CheckIsNumericFromNumpad(e.raw); e.IsNumericFromNumbers = CheckIsNumericFromNumbers(e.raw); e.IsNoUnicodekey = CheckIsNoUnicodekey(e.raw); e.IsFunctionKey = CheckIsFunctionKey(e.raw); e.ModifierToggledEvent = CheckKeyIsModifier(e.raw); Log.e("KP", " hook_KeyEvent(): Called. IsAlpha=" + e.IsAlpha.ToString() + " e.Key=" + (e.Key).ToString()); if (e.Method == KeyUpDown.Down) { ApplyDeadKey(e); if (e.IsAlpha && e.OnlyShiftOrCaps) { Log.e("KP", " hook_KeyEvent(): IsAlpha and OnlyShiftOrCaps => ParseChar"); e.TextModeString = ParseChar(e); e.ShouldBeDisplayed = true; e.StrokeType = KeystrokeType.Text; e.Deletable = true; } else if (e.IsNumeric && e.NoModifiers) { Log.e("KP", " hook_KeyEvent(): e.IsNumeric && e.NoModifiers => ParseNumeric"); e.TextModeString = ParseNumeric(e); e.ShouldBeDisplayed = true; e.StrokeType = KeystrokeType.Text; e.Deletable = true; } else if (e.ModifierToggledEvent) // key is modifier { Log.e("KP", " hook_KeyEvent(): e.ModifierToggledEvent => AddModifier(" + e.Key.ToString() + ")"); e.ShouldBeDisplayed = false; AddModifier(e.Key, e); e.StrokeType = KeystrokeType.Modifiers; } else if (e.IsNoUnicodekey && e.NoModifiers) { Log.e("KP", " hook_KeyEvent(): e.IsNoUnicodekey && e.NoModifiers => ParseTexttViaSpecialkeysParser "); ParseTexttViaSpecialkeysParser(e); e.Deletable = IsDeletableSpecialKey(e.Key); } else if (e.IsNoUnicodekey && !e.NoModifiers) // Shortcut { Log.e("KP", " hook_KeyEvent(): e.IsNoUnicodekey && !e.NoModifiers => ParseShortcutViaSpecialkeysParser "); ParseShortcutViaSpecialkeysParser(e); } else if (e.NoModifiers) // Simple Key, but not alphanumeric (first try special then unicode) { Log.e("KP", " hook_KeyEvent(): e.NoModifiers => try SpecialkeysParser.ToString "); try { e.TextModeString = SpecialkeysParser.ToString(e.Key); } catch (NotImplementedException) { Log.e("KP", " hook_KeyEvent(): failed, now => try KeyboardLayoutParser.ParseViaToUnicode "); e.TextModeString = KeyboardLayoutParser.ParseViaToUnicode(e.raw); BackupDeadKey(e); } e.ShouldBeDisplayed = true; e.StrokeType = KeystrokeType.Text; e.RequiresNewLineAfterwards = e.Key == Key.Return; } else if (e.OnlyShiftOrCaps) // special char, but only Shifted, eg ;:_ÖÄ'*ÜP // (e.IsNoUnicodekey is always false here -> could be a unicode key combination) { Log.e("KP", " hook_KeyEvent(): e.OnlyShiftOrCaps => try KeyboardLayoutParser.ParseViaToUnicode "); e.TextModeString = KeyboardLayoutParser.ParseViaToUnicode(e.raw); BackupDeadKey(e); if (e.TextModeString != "") { e.ShouldBeDisplayed = true; e.StrokeType = KeystrokeType.Text; e.Deletable = true; } else { // Is no unicode key combination? maybe a shortcut then: Shift + F2 Log.e("KP", " hook_KeyEvent(): failed, now => ParseShortcutViaSpecialkeysParser "); ParseShortcutViaSpecialkeysParser(e); } } else if (!e.NoModifiers && !e.OnlyShiftOrCaps) // Special Char with Strg + Alt // or shortcut else { // could be something like the german @ (Ctrl + Alt + Q) Log.e("KP", " hook_KeyEvent(): !e.NoModifiers && !e.OnlyShiftOrCaps => (KeyboardLayoutParser.ParseViaToUnicode) (disabled)"); // Temporary disabled because ToUnicode returns more often values than it should e.TextModeString = ""; //KeyboardLayoutParser.ParseViaToUnicode(e); // all other special char keycodes do not use Shift if (e.TextModeString != "" && !e.Shift && !e.IsNoUnicodekey) { e.ShouldBeDisplayed = true; e.StrokeType = KeystrokeType.Text; e.Deletable = true; } else // Shortcut { Log.e("KP", " hook_KeyEvent(): !e.NoModifiers && !e.OnlyShiftOrCapss => ParseShortcutViaSpecialkeysParser "); ParseShortcutViaSpecialkeysParser(e); string possibleChar = KeyboardLayoutParser.ParseViaToUnicode(e.raw); BackupDeadKey(e); if (possibleChar != "" && !CheckIsAlpha(possibleChar) && !CheckIsNumeric(possibleChar) && CheckIsASCII(possibleChar)) { e.TextModeString += " (" + possibleChar + ")"; } } } e.ShortcutString = e.AsShortcutString(); Log.e("KP", " hook_KeyEvent(): Result: TextmodeString=" + e.TextModeString + " ShortcutString=" + e.ShortcutString); } if (e.Method == KeyUpDown.Up) { if (e.ModifierToggledEvent) // key is modifier { e.ShouldBeDisplayed = false; RemoveModifier(e.Key, e); e.StrokeType = KeystrokeType.Modifiers; } Log.e("KP", " hook_KeyEvent(): Key up. e.Key=" + (e.Key).ToString()); // only react to modifiers on key up, nothing else } if (e.StrokeType != KeystrokeType.Undefined) { OnKeystrokeEvent(e); } } private KeystrokeEventArgs lastDeadKeyEvent; private void BackupDeadKey(KeystrokeEventArgs e) { if(e.TextModeString == "DEADKEY") { e.TextModeString = "!"; lastDeadKeyEvent = e; } } private bool ApplyDeadKey(KeystrokeEventArgs e) { if(lastDeadKeyEvent != null) { Log.e("KP", " ApplyDeadKey(): Process Previous Dead Key: " + lastDeadKeyEvent.ToString()); lastDeadKeyEvent.TextModeString = KeyboardLayoutParser.ProcessDeadkeyWithNextKey(lastDeadKeyEvent.raw, e.raw); if(lastDeadKeyEvent.TextModeString == "") { lastDeadKeyEvent = null; return false; } OnKeystrokeEvent(lastDeadKeyEvent); lastDeadKeyEvent = null; return true; } return false; } private void ParseShortcutViaSpecialkeysParser(KeystrokeEventArgs e) { try { e.TextModeString = SpecialkeysParser.ToString(e.Key); } catch (NotImplementedException) { e.TextModeString = e.Key.ToString(); } e.ShouldBeDisplayed = true; e.StrokeType = KeystrokeType.Shortcut; e.RequiresNewLine = true; e.RequiresNewLineAfterwards = true; } private void ParseTexttViaSpecialkeysParser(KeystrokeEventArgs e) { try { e.TextModeString = SpecialkeysParser.ToString(e.Key); e.ShouldBeDisplayed = true; e.StrokeType = KeystrokeType.Text; e.RequiresNewLineAfterwards = e.Key == Key.Return; } catch (NotImplementedException) { } } private void AddModifier(Key keys, KeystrokeEventArgs e) { switch (keys) { case Key.LeftCtrl: e.LCtrl = true; e.Ctrl = true; break; case Key.RightCtrl: e.RCtrl = true; e.Ctrl = true; break; case Key.LeftShift: e.LShift = true; e.Shift = true; break; case Key.RightShift: e.RShift = true; e.Shift = true; break; case Key.LWin: e.LWin = true; break; case Key.RWin: e.RWin = true; break; case Key.LeftAlt: e.LAlt = true; e.Alt = true; break; case Key.RightAlt: e.RAlt = true; e.Alt = true; break; case Key.NumLock: e.Numlock = true; break; case Key.Scroll: e.Scrollock = true; break; case Key.CapsLock: e.Caps = true; break; } } private void RemoveModifier(Key keys, KeystrokeEventArgs e) { // If Left Shift is released, then only unset Shift if RShift is not set switch (keys) { case Key.LeftCtrl: e.LCtrl = false; e.Ctrl = e.RCtrl; break; case Key.RightCtrl: e.RCtrl = false; e.Ctrl = e.LCtrl; break; case Key.LeftShift: e.LShift = false; e.Shift = e.RShift; break; case Key.RightShift: e.RShift = false; e.Shift = e.LShift; break; case Key.LWin: e.LWin = false; break; case Key.RWin: e.RWin = false; break; case Key.LeftAlt: e.LAlt = false; e.Alt = e.RAlt; break; case Key.RightAlt: e.RAlt = false; e.Alt = e.LAlt; break; case Key.NumLock: e.Numlock = false; break; case Key.Scroll: e.Scrollock = false; break; case Key.CapsLock: e.Caps = false; break; } } private string ParseChar(KeystrokeEventArgs e) { string c = e.Key.ToString(); if (!e.Uppercase) c = c.ToLower(); // We parse the Shift and Caps keys into the Upper/Lowercase e.Shift = false; e.Caps = false; e.LShift = false; e.RShift = false; return c; } private string ParseNumeric(KeystrokeEventArgs e) { if (e.IsNumericFromNumbers) { return ((int)e.Key - (int)Key.D0).ToString(); } else if (e.IsNumericFromNumpad) { return ((int)e.Key - (int)Key.NumPad0).ToString(); } return "BUG1"; } bool CheckIsAlpha(KeyboardRawEventArgs e) { return CheckIsAlpha(e.Key); } bool CheckIsAlpha(Key key) { return ((int)Key.A <= (int)key && (int)key <= (int)Key.Z); } bool CheckIsAlpha(string s) { return (s.Length == 1 && ( (s[0] >= 'a' && s[0] <= 'z') || (s[0] >= 'A' && s[0] <= 'Z'))); } bool CheckIsASCII(string s) { return (s.Length == 1 && ((s[0] >= 32 && s[0] <= 126) )); } bool CheckIsNumericFromNumpad(KeyboardRawEventArgs e) { return CheckIsNumericFromNumpad(e.Key); } bool CheckIsNumericFromNumpad(Key key) { return ((int)Key.NumPad0 <= (int)key && (int)key <= (int)Key.NumPad9); } bool CheckIsNumeric(string s) { return (s.Length == 1 && (s[0] >= '0' && s[0] <= '9')); } bool CheckIsNumericFromNumbers(KeyboardRawEventArgs e) { return CheckIsNumericFromNumbers(e.Key); } bool CheckIsNumericFromNumbers(Key key) { return ((int)Key.D0 <= (int)key && (int)key <= (int)Key.D9); } bool CheckIsFunctionKey(KeyboardRawEventArgs e) { return ((int)Key.F1 <= (int)e.Key && (int)e.Key <= (int)Key.F24); } /// /// If this function returns true, there will be no attemt to decode the key press /// via ToUnicode to reveal Strg+Alt+E = € . /// /// /// bool CheckIsNoUnicodekey(KeyboardRawEventArgs e) { Key[] NoUnicodeKeys = { Key.Cancel, Key.Tab, Key.LineFeed, Key.Clear, Key.Enter, Key.Return, Key.Pause, Key.CapsLock, Key.Capital, Key.KanaMode, Key.JunjaMode, Key.FinalMode, Key.KanjiMode, Key.HanjaMode, Key.Escape, Key.ImeConvert, Key.ImeNonConvert, Key.ImeAccept, Key.ImeModeChange, Key.Space, Key.Prior, Key.PageUp, Key.Next, Key.PageDown, Key.End, Key.Home, Key.Left, Key.Up, Key.Right, Key.Down, Key.Select, Key.Print, Key.Execute, Key.PrintScreen, Key.Snapshot, Key.Insert, Key.Delete, Key.Help, Key.LWin, Key.RWin, Key.Apps, Key.Sleep, Key.Multiply, Key.Add, Key.Separator, Key.Subtract, Key.Decimal, Key.Divide, Key.F1, Key.F2, Key.F3, Key.F4, Key.F5, Key.F6, Key.F7, Key.F8, Key.F9, Key.F10, Key.F11, Key.F12, Key.F13, Key.F14, Key.F15, Key.F16, Key.F17, Key.F18, Key.F19, Key.F20, Key.F21, Key.F22, Key.F23, Key.F24, Key.NumLock, Key.Scroll, Key.LeftShift, Key.RightShift, Key.LeftCtrl, Key.RightCtrl, Key.LeftAlt, Key.RightAlt, Key.BrowserBack, Key.BrowserForward, Key.BrowserRefresh, Key.BrowserStop, Key.BrowserSearch, Key.BrowserFavorites, Key.BrowserHome, Key.VolumeMute, Key.VolumeDown, Key.VolumeUp, Key.MediaNextTrack, Key.MediaPreviousTrack, Key.MediaStop, Key.MediaPlayPause, Key.LaunchMail, Key.SelectMedia, Key.LaunchApplication1, Key.LaunchApplication2, Key.ImeProcessed, 0, Key.Attn, Key.CrSel, Key.ExSel, Key.EraseEof, Key.Play, Key.Zoom, Key.NoName, Key.Pa1, Key.OemClear }; return NoUnicodeKeys.Contains(e.Key); } private bool IsDeletableSpecialKey(Key key) { Key[] DeletableKeys = { Key.Multiply, Key.Add, Key.Subtract, Key.Decimal, Key.Divide, Key.Space}; return DeletableKeys.Contains(key); } bool CheckKeyIsModifier(KeyboardRawEventArgs e) { Key[] ModifierKeys = { Key.LeftShift, Key.RightShift, Key.LeftCtrl, Key.RightCtrl, Key.LeftAlt, Key.RightAlt, Key.LWin, Key.RWin, Key.NumLock, Key.Scroll, Key.CapsLock}; return ModifierKeys.Contains(e.Key); } #region Event Forwarding public event KeystrokeEventHandler KeystrokeEvent; private void OnKeystrokeEvent(KeystrokeEventArgs e) { if (KeystrokeEvent != null) { KeystrokeEvent(e); } } #endregion } } ================================================ FILE: KeyNStroke/LabeledSlider.py ================================================ import numpy as np import matplotlib.pyplot as plt import math from matplotlib.widgets import Slider # LabeledSlider.Value X # XX # f(x) ^ + XX # | | XX # | | XX # ymax +--+ +------XX------+ # | XX # | X| # | XX | # | X + # | XX # | XXX # | XX # | XXX # | XXX # | XX # | + XXX # | | XXXX # | | XXXX #ys(ysper) +--+ +---XXXX--+ # | XXXX | # | XXXXXX | # | XXXXXXXX | # ymin +--XXXXXXXXXXX | # | + # | # | # +----------------------------+--------------------------+-----------------------> inner Slider.Value # | | | x # + + + # x0=0 xs=x1/2 x1 # # # y = f(x) = a + b * exp(c * x) # # Calculate a, b, c from the constants ymin, ys, ymax, x1 so that f(x) fulfills (1), (2) and (3). # # (1) f(x = x0 = 0) = ymin # (2) f(x = xs = x1 / 2) = ys # (3) f(x = x1) = ymax # # xs is not free to chose but set to x1/2 so that while solving the equations, we magically get a quadratic equation. # # (1) ymin = a + b # (2) ys = a + b * exp(c * x1 / 2) # (3) ymax = a + b * exp(c * x1) # # (1) for a (4) a = ymin - b # (4) in (2) (5) ys = ymin - b + b * exp(c * x1 / 2) # (4) in (3) (6) ymax = ymin - b + b * exp(c * x1) # -ymin (6) ymax - ymin = b * (exp(c * x1) - 1) # solve (5) for b (7) b = (ys - ymin) / (exp(c * x1 / 2) - 1) # (7) in (6) (8) ymax - ymin = (ys - ymin) / (exp(c * x1 / 2) - 1) * (exp(c * x1) - 1) # *exp(..), -rhs (8) (ymax - ymin) * (exp(c * x1 / 2) - 1) - (ys - ymin) * (exp(c * x1) - 1) = 0 # set (9) d = (ymin - ymax) / (ys - ymin) # substitute (9) in (8) (10) exp(c * x1 ) + d * e(c * x1 / 2) + (-1 - d) = 0 # in first exp: /2*2 (10) exp(c * x1 / 2) ** 2 + d * e(c * x1 / 2) + (-1 - d) = 0 # set (11) v = exp(c * x1 / 2) # substitute (11) in (10) (12) v ** 2 + d * v + (-1 - d) == 0 # solve quadratic eq (12) (13) v1,2 = - (d/2) +- sqrt( (d/2)**2 - (-1-d) ) # solve (11) for c (14) c = 2 * ln(g) / x1 # # calculate v1 with (13) and (9). Use (14), (7), (4) to calculate a,b,c fig, ax = plt.subplots() plt.subplots_adjust(bottom=0.25) ymax = 120. ymin = 3. #ys= ysper = 0.2 # value from 0 to 1 that gets mapped to the intervall [ymin+1%, ((ymin+ymax/2)-1%] and used as ys x0=0. x1=1000. xs=x1/2 def pq(p, q): r1 = (-(p/2)) r2 = np.sqrt((p/2)**2 - q) return (r1 + r2, r1 - r2) def calc(ysper): ysmin = ymin * 1.01 ysmax = ((ymin+ymax)/2) * 0.99 ys = ysmin + ysper * (ysmax - ysmin) d=(ymin-ymax)/(ys-ymin) v1, v2 = pq(d, -1-d) c = 2 * np.log(v1) / x1 b = (ys - ymin) / (np.exp(c*x1/2) - 1) a = ymin - b t = np.exp(c*x1) + 1 + d * np.exp(c*x1/2) - d t1 = (ymax-ymin) * (np.exp(c*x1/2)-1) - (ys-ymin) * (np.exp(c*x1)-1) print(f"v1={v1:.3} v2={v2:.3} a={a:.3} b={b:.3} c={c:.3} f(x0)=a+b={a+b:.3}=={ymin:.3}" f" f(xs)=a+bexp(c*x1/2)={a+b*np.exp(c*x1/2):.3}=={ys:.3}" f" f(x1)=a+bexp(c*x1)={a+b*np.exp(c*x1):.3}=={ymax:.3}") x = np.arange(x0, x1, (x1-x0)/1000) y = a + b * np.exp(c*x) return x, y, ys x, y, ys = calc(ysper) l1, = plt.plot(x, y) plt.plot([x0, x1], [ymax, ymax]) plt.plot([x0, x1], [ymin, ymin]) l2, = plt.plot([x0, x1], [ys, ys]) plt.plot([x0, x0], [0, ymax]) plt.plot([x1, x1], [0, ymax]) plt.plot([xs, xs], [0, ymax]) def update(ysper): x, y, ys = calc(ysper) l1.set_ydata(y) l1.set_xdata(x) l2.set_ydata([ys, ys]) axys = plt.axes([0.25, 0.1, 0.65, 0.03]) sys = Slider(axys, 'ys %', 0, 1.0, valinit=ysper) sys.on_changed(update) plt.show() ================================================ FILE: KeyNStroke/LabeledSlider.xaml ================================================  ================================================ FILE: KeyNStroke/LabeledSlider.xaml.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel; using System.Globalization; 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.Navigation; using System.Windows.Shapes; namespace KeyNStroke { /// /// Interaktionslogik für LabeledSlider.xaml /// public partial class LabeledSlider : UserControl { private bool interalValueUpdate = false; public LabeledSlider() { InitializeComponent(); Log.e("BIN", "Set Data context in labeled slider"); layout_root.DataContext = this; var dpd = DependencyPropertyDescriptor.FromProperty(ValueProperty, typeof(LabeledSlider)); dpd.AddValueChanged(this, (object sender, EventArgs e) => { Log.e("SLIDER", $"{Title}: Value={Value}"); ValueToSlider(); }); var dpd1 = DependencyPropertyDescriptor.FromProperty(MinimumProperty, typeof(LabeledSlider)); dpd1.AddValueChanged(this, (object sender, EventArgs e) => { recalcParams(); ValueToSlider(); }); var dpd2 = DependencyPropertyDescriptor.FromProperty(MaximumProperty, typeof(LabeledSlider)); dpd2.AddValueChanged(this, (object sender, EventArgs e) => { recalcParams(); ValueToSlider(); }); var dpd3 = DependencyPropertyDescriptor.FromProperty(HalfWayProperty, typeof(LabeledSlider)); dpd3.AddValueChanged(this, (object sender, EventArgs e) => { recalcParams(); ValueToSlider(); }); var dpd4 = DependencyPropertyDescriptor.FromProperty(LogarithmicProperty, typeof(LabeledSlider)); dpd4.AddValueChanged(this, (object sender, EventArgs e) => { recalcParams(); ValueToSlider(); }); } [Browsable(true)] public double HalfWay { get { return (double)GetValue(HalfWayProperty); } set { SetValue(HalfWayProperty, value); } } public static readonly DependencyProperty HalfWayProperty = DependencyProperty.Register( "HalfWay", typeof(double), typeof(LabeledSlider) ); [Browsable(true)] public bool Logarithmic { get { return (bool)GetValue(LogarithmicProperty); } set { SetValue(LogarithmicProperty, value); } } public static readonly DependencyProperty LogarithmicProperty = DependencyProperty.Register( "Logarithmic", typeof(bool), typeof(LabeledSlider) ); [Browsable(true)] public String Title { get { return (String) GetValue(TitleProperty); } set { SetValue(TitleProperty, value); } } public static readonly DependencyProperty TitleProperty = DependencyProperty.Register( "Title", typeof(String), typeof(LabeledSlider) ); [Browsable(true)] public double Minimum { get { return (double) GetValue(MinimumProperty); } set { SetValue(MinimumProperty, value); } } public static readonly DependencyProperty MinimumProperty = DependencyProperty.Register("Minimum", typeof(double), typeof(LabeledSlider), new PropertyMetadata(0.0)); [Browsable(true)] public double Maximum { get { return (double) GetValue(MaximumProperty); } set { SetValue(MaximumProperty, value); } } public static readonly DependencyProperty MaximumProperty = DependencyProperty.Register("Maximum", typeof(double), typeof(LabeledSlider), new PropertyMetadata(0.0)); [Browsable(true)] public double Value { get { return (double) GetValue(ValueProperty); } set { SetValue(ValueProperty, value); } } public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(LabeledSlider), new PropertyMetadata(0.0)); private double pq(double p, double q) { double r1 = -(p / 2); double r2 = Math.Sqrt(Math.Pow(p / 2, 2) - q); return r1 + r2; } private double a; private double b; private double c; double m; private void recalcParams() { double ymin = Minimum; double ymax = Maximum; double ysper = Math.Min(1.0, Math.Max(0.0, HalfWay)); double x1 = slider.Maximum; if (ymax == ymin) { ymax = 10 + ymin; } double ysmin = ymin * 1.01; double ysmax = ((ymin + ymax) / 2) * 0.99; double ys = ysmin + ysper * (ysmax - ysmin); double d = (ymin - ymax) / (ys - ymin); double v1 = pq(d, -1 - d); c = 2 * Math.Log(v1) / x1; b = (ys - ymin) / (Math.Exp(c * x1 / 2) - 1); a = ymin - b; // linear m = (ymax - ymin) / x1; Log.e("SLIDER", $"{Title}: ymax={ymax} ymin={ymin} x1={x1} m={m}"); if (m == 0) { m = 0.1; } } private void ValueToSlider() { if (!interalValueUpdate) { interalValueUpdate = true; double y = Math.Min(Maximum, Math.Max(Minimum, Value)); if (Logarithmic) { slider.Value = Math.Log((y - a) / b) / c; } else { slider.Value = (y - Minimum) / m; } interalValueUpdate = false; } } private void SliderToValue() { if (!interalValueUpdate) { interalValueUpdate = true; if (Logarithmic) { Value = a + b * Math.Exp(c * slider.Value); } else { Value = Minimum + m * slider.Value; Log.e("SLIDER", $"{Title}: {Value} = {Minimum} {m} * {slider.Value}"); } interalValueUpdate = false; } } public event RoutedPropertyChangedEventHandler ValueChanged; private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) { SliderToValue(); RoutedPropertyChangedEventArgs eint = null; if (e != null) { eint = new RoutedPropertyChangedEventArgs(e.OldValue, e.NewValue, e.RoutedEvent); } if (ValueChanged != null) { ValueChanged.Invoke(sender, eint); } } } } ================================================ FILE: KeyNStroke/Log.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace KeyNStroke { public class Log { static string TagFilter = ""; static string[] allowed = new string[]{}; public static void SetTagFilter(string tagfilter) { TagFilter = tagfilter; allowed = TagFilter.Split(new char[] { ',', '|' }, StringSplitOptions.RemoveEmptyEntries); } static bool filter(string tag) { return tag == TagFilter || TagFilter == "" || allowed.Contains(tag); } public static void e(string tag, string msg) { if(filter(tag)) { Console.WriteLine(tag.ToUpper() + " " + msg); } } public static void e(string msg) { e("ERR", msg); } } } ================================================ FILE: KeyNStroke/MouseHook.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using KeyNStroke; namespace KeyNStroke { /// /// Low level mouse event interception class /// public class MouseHook : IDisposable, IMouseRawEventProvider { #region Initializion int doubleClickTime; SettingsStore s; /// /// Set up the mouse hook /// public MouseHook(SettingsStore s) { this.s = s; doubleClickTime = NativeMethodsMouse.GetDoubleClickTime(); Log.e("ME", "doubleClickTime " + doubleClickTime.ToString()); RegisterMouseHook(); } #endregion #region Hook Add/Remove //Variables used in the call to SetWindowsHookEx private NativeMethodsMouse.HookHandlerDelegate windowsHookExProc; private IntPtr windowsHookExID = IntPtr.Zero; //Variables used in the call to SetWinEventHook private NativeMethodsEvents.WinEventDelegate setWinEventHookProc; private IntPtr setWinEventHookID = IntPtr.Zero; /// /// Registers the function WindowsHookExCallback for the global mouse events winapi. /// Also registers a WinEventHook to check if the cursor is hidden. /// private void RegisterMouseHook() { // Register WindowsHookExCallback if (windowsHookExID == IntPtr.Zero) { windowsHookExProc = new NativeMethodsMouse.HookHandlerDelegate(WindowsHookExCallback); using (Process curProcess = Process.GetCurrentProcess()) { using (ProcessModule curModule = curProcess.MainModule) { windowsHookExID = NativeMethodsMouse.SetWindowsHookEx(NativeMethodsMouse.WH_MOUSE_LL, windowsHookExProc, NativeMethodsKeyboard.GetModuleHandle(curModule.ModuleName), 0); } } } // Register WinEventCallback // https://devblogs.microsoft.com/oldnewthing/20151116-00/?p=92091 if (setWinEventHookID == IntPtr.Zero) { setWinEventHookProc = new NativeMethodsEvents.WinEventDelegate(WinEventCallback); setWinEventHookID = NativeMethodsEvents.SetWinEventHook( NativeMethodsEvents.WinEvents.EVENT_OBJECT_SHOW, NativeMethodsEvents.WinEvents.EVENT_OBJECT_NAMECHANGE, IntPtr.Zero, setWinEventHookProc, 0, // ProcessId = 0 and ThreadId = 0 -> global events 0, NativeMethodsEvents.WinEventFlags.WINEVENT_SKIPOWNPROCESS ); } } /// /// Unregisters the winapi hook for global mouse events /// private void UnregisterMouseHook() { if (windowsHookExID != IntPtr.Zero) { NativeMethodsMouse.UnhookWindowsHookEx(windowsHookExID); windowsHookExID = IntPtr.Zero; } if (setWinEventHookID != IntPtr.Zero) { NativeMethodsEvents.UnhookWinEvent(setWinEventHookID); setWinEventHookID = IntPtr.Zero; } } #endregion #region Event Handling MouseRawEventArgs lastDownEvent; /// /// Processes the key event captured by the hook. /// private IntPtr WindowsHookExCallback(int nCode, UIntPtr wParam, ref NativeMethodsMouse.MSLLHOOKSTRUCT lParam) { if (nCode < 0) { return NativeMethodsMouse.CallNextHookEx(windowsHookExID, nCode, wParam, ref lParam); } else { if (nCode == NativeMethodsMouse.HC_ACTION) { MouseRawEventArgs args = new MouseRawEventArgs(lParam); args.ParseWparam(wParam); CheckDoubleClick(args); Log.e("ME", String.Format("MOUSE: Button:{0} Action:{1} Orig:{2}", args.Button.ToString(), args.Action.ToString(), args.Event.ToString())); OnMouseEvent(args); if (args.preventDefault) { return IntPtr.Add(IntPtr.Zero, 1); } else { return NativeMethodsMouse.CallNextHookEx(windowsHookExID, nCode, wParam, ref lParam); } } else { return NativeMethodsMouse.CallNextHookEx(windowsHookExID, nCode, wParam, ref lParam); } } } private void CheckDoubleClick(MouseRawEventArgs args) { if (lastDownEvent != null && args.Action == MouseAction.Down) { if (args.Button == lastDownEvent.Button && args.Msllhookstruct.time <= lastDownEvent.Msllhookstruct.time + doubleClickTime && args.Msllhookstruct.pt.Equals(lastDownEvent.Msllhookstruct.pt)) { args.Action = MouseAction.DblClk; Log.e("ME", "DBLCLK"); } } if (args.Action == MouseAction.Down) lastDownEvent = args; } private void WinEventCallback(IntPtr hWinEventHook, NativeMethodsEvents.WinEvents eventType, IntPtr hwnd, uint idObject, uint idChild, uint dwEventThread, uint dwmsEventTime) { if (hwnd == IntPtr.Zero && idObject == (uint)NativeMethodsEvents.ObjIds.OBJID_CURSOR && idChild == NativeMethodsEvents.CHILDID_SELF) { switch (eventType) { case NativeMethodsEvents.WinEvents.EVENT_OBJECT_HIDE: OnCursorEvent(false); break; case NativeMethodsEvents.WinEvents.EVENT_OBJECT_SHOW: OnCursorEvent(true); break; case NativeMethodsEvents.WinEvents.EVENT_OBJECT_NAMECHANGE: if (s.CursorIndicatorHideIfCustomCursor) { NativeMethodsMouse.CURSORINFO info = NativeMethodsMouse.GetCursorInfoWrapper(); Log.e("CURSOR", $"EVENT_OBJECT_NAMECHANGE flags={info.flags} hCursor={info.hCursor}"); if (info.flags == NativeMethodsMouse.CURSOR_HIDDEN || ((uint)info.hCursor) >= 0x100000) /* limit guessed */ { OnCursorEvent(false); } else { OnCursorEvent(true); } } break; } } } #endregion #region Event Forwarding /// /// Fires if mouse is moved or clicked. /// public event MouseRawEventHandler MouseEvent; /// /// Raises the MouseEvent event. /// /// An instance of MouseRawEventArgs public void OnMouseEvent(MouseRawEventArgs e) { if (MouseEvent != null) MouseEvent(e); } /// /// Fires if cursor is shown or hidden. /// public event CursorEventHandler CursorEvent; /// /// Raises the CursorEvent event. /// /// True if the cursor is visible public void OnCursorEvent(bool visible) { if (CursorEvent != null) CursorEvent(visible); } #endregion #region Finalizing and Disposing ~MouseHook() { Log.e("HOOK", "~MouseHook"); UnregisterMouseHook(); } /// /// Releases the mouse hook. /// public void Dispose() { Log.e("HOOK", "Dispose MouseHook"); UnregisterMouseHook(); } #endregion } } ================================================ FILE: KeyNStroke/MouseRawEvent.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using KeyNStroke; namespace KeyNStroke { public enum MouseEventType { MOUSEMOVE, LBUTTONDOWN, LBUTTONUP, LBUTTONDBLCLK, RBUTTONDOWN, RBUTTONUP, RBUTTONDBLCLK, MBUTTONDOWN, MBUTTONUP, MBUTTONDBLCLK, MOUSEWHEEL1, XBUTTONDOWN, XBUTTONUP, XBUTTONDBLCLK, MOUSEHWHEEL2 } public enum MouseButton { LButton, RButton, MButton, XButton, None } public enum MouseAction { Up, Down, DblClk, Move, Wheel, } public class MouseRawEventArgs { public NativeMethodsMouse.MSLLHOOKSTRUCT Msllhookstruct; public MouseEventType Event; public MouseButton Button; public MouseAction Action; public int wheelDelta = 0; public bool preventDefault = false; public MouseRawEventArgs(NativeMethodsMouse.MSLLHOOKSTRUCT msllhookstruct) { this.Msllhookstruct = msllhookstruct; } public NativeMethodsMouse.POINT Position { get { return Msllhookstruct.pt; } } public void ParseWparam(UIntPtr wParam) { switch((int) wParam) { case NativeMethodsMouse.WM_MOUSEMOVE: Event = MouseEventType.MOUSEMOVE; Button = MouseButton.None; Action = MouseAction.Move; break; case NativeMethodsMouse.WM_LBUTTONDOWN: Event = MouseEventType.LBUTTONDOWN; Button = MouseButton.LButton; Action = MouseAction.Down; break; case NativeMethodsMouse.WM_LBUTTONUP: Event = MouseEventType.LBUTTONUP; Button = MouseButton.LButton; Action = MouseAction.Up; break; case NativeMethodsMouse.WM_LBUTTONDBLCLK: Event = MouseEventType.LBUTTONDBLCLK; Button = MouseButton.LButton; Action = MouseAction.DblClk; break; case NativeMethodsMouse.WM_RBUTTONDOWN: Event = MouseEventType.RBUTTONDOWN; Button = MouseButton.RButton; Action = MouseAction.Down; break; case NativeMethodsMouse.WM_RBUTTONUP: Event = MouseEventType.RBUTTONUP; Button = MouseButton.RButton; Action = MouseAction.Up; break; case NativeMethodsMouse.WM_RBUTTONDBLCLK: Event = MouseEventType.RBUTTONDBLCLK; Button = MouseButton.RButton; Action = MouseAction.DblClk; break; case NativeMethodsMouse.WM_MBUTTONDOWN: Event = MouseEventType.MBUTTONDOWN; Button = MouseButton.MButton; Action = MouseAction.Down; break; case NativeMethodsMouse.WM_MBUTTONUP: Event = MouseEventType.MBUTTONUP; Button = MouseButton.MButton; Action = MouseAction.Up; break; case NativeMethodsMouse.WM_MBUTTONDBLCLK: Event = MouseEventType.MBUTTONDBLCLK; Button = MouseButton.MButton; Action = MouseAction.DblClk; break; case NativeMethodsMouse.WM_XBUTTONDOWN: Event = MouseEventType.XBUTTONDOWN; Button = MouseButton.XButton; Action = MouseAction.Down; break; case NativeMethodsMouse.WM_XBUTTONUP: Event = MouseEventType.XBUTTONUP; Button = MouseButton.XButton; Action = MouseAction.Up; break; case NativeMethodsMouse.WM_XBUTTONDBLCLK: Event = MouseEventType.XBUTTONDBLCLK; Button = MouseButton.XButton; Action = MouseAction.DblClk; break; case NativeMethodsMouse.WM_MOUSEWHEEL1: Event = MouseEventType.MOUSEWHEEL1; Button = MouseButton.None; Action = MouseAction.Wheel; unchecked { wheelDelta = BitConverter.ToInt16(BitConverter.GetBytes(Msllhookstruct.mouseData), 2); } break; case NativeMethodsMouse.WM_MOUSEHWHEEL2: Event = MouseEventType.MOUSEHWHEEL2; Button = MouseButton.None; Action = MouseAction.Wheel; unchecked { wheelDelta = BitConverter.ToInt16(BitConverter.GetBytes(Msllhookstruct.mouseData), 2); } break; default: Log.e("ME", "Unknown Mouse Event: " + wParam.ToString()); break; } } } public delegate void MouseRawEventHandler(MouseRawEventArgs raw_e); public delegate void CursorEventHandler(bool visible); public interface IMouseRawEventProvider : IDisposable { event MouseRawEventHandler MouseEvent; event CursorEventHandler CursorEvent; } } ================================================ FILE: KeyNStroke/NativeMethodsDC.cs ================================================ using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using KeyNStroke; namespace KeyNStroke { // https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features?WT.mc_id=DT-MVP-5003235#layered-windows // https://docs.microsoft.com/en-us/windows/win32/gdi/about-device-contexts // GDI: Graphics Device Interface // = Some API to paint on screens, printers, ... // DC: DeviceContext // - https://docs.microsoft.com/de-de/windows/win32/gdi/about-bitmaps // - Contains a set of graphic objects: // - Bitmaps or Paths or Rectangles or Polygons or Text // with associated Fonts, Brushes, Pens, Color Palettes (logical palettes) // - Each graphic object is contained at most once, and is otherwise defaulted or null // - SelectObject replaces the object of the selected type in the DC // https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-selectobject // - This function returns the previously selected object of the specified type. // An application should always replace a new object with the original, // default object after it has finished drawing with the new object. // - An application cannot select a single bitmap into more than one DC at a time. // GDI Bitmaps need to be created from .NET Bitmaps.. -> todo: cache or measure // https://docs.microsoft.com/de-de/dotnet/api/system.drawing.bitmap.gethbitmap?view=netframework-4.8 // GDI Bitmaps consist of: // A header that describes the resolution of the device on which the rectangle of pixels was created, the dimensions of the rectangle, the size of the array of bits, and so on. // A logical palette. // An array of bits that defines the relationship between pixels in the bitmapped image and entries in the logical palette. // https://docs.microsoft.com/de-de/windows/win32/gdi/bitmaps?redirectedfrom=MSDN public class NativeMethodsDC { /// /// /// /// Window Handle /// Position of window /// Bitmap to set /// Better performance with 1.0 public static void SetBitmapForWindow(IntPtr windowHandle, Point pos, Bitmap bitmap, float opacity) { // Does this bitmap contain an alpha channel? if (bitmap.PixelFormat != PixelFormat.Format32bppArgb) { throw new ApplicationException("The bitmap must be 32bpp with alpha-channel."); } // Get device contexts IntPtr screenDc = GetDC(IntPtr.Zero); IntPtr memDc = CreateCompatibleDC(screenDc); IntPtr hBitmap = IntPtr.Zero; IntPtr hOldBitmap = IntPtr.Zero; opacity = Math.Min(opacity, 1.0f); opacity = Math.Max(opacity, 0.0f); try { // Get handle to the new bitmap and select it into the current // device context. (keep the old bitmap because the documentation // wants us to restore the Bitmap after drawing) hBitmap = bitmap.GetHbitmap(Color.FromArgb(0)); hOldBitmap = SelectObject(memDc, hBitmap); // Set parameters for layered window update. SIZE newSize = new SIZE(bitmap.Width, bitmap.Height); POINT sourceLocation = new POINT(0, 0); POINT newLocation = new POINT(pos.X, pos.Y); // new Point(this.Left, this.Top) BLENDFUNCTION blend = new BLENDFUNCTION(); blend.BlendOp = AC_SRC_OVER; blend.BlendFlags = 0; blend.SourceConstantAlpha = (byte) (int) (opacity * 255); blend.AlphaFormat = AC_SRC_ALPHA; Log.e("LAYWIN", "Draw to " + pos.ToString() + " and set size to " + bitmap.Width.ToString() + " " + bitmap.Height.ToString()); // Update the window. UpdateLayeredWindow( windowHandle, // Handle to the layered window screenDc, // Handle to the screen DC ref newLocation, // New screen position of the layered window ref newSize, // New size of the layered window memDc, // Handle to the layered window surface DC ref sourceLocation, // Location of the layer in the DC 0, // Color key of the layered window ref blend, // Transparency of the layered window ULW_ALPHA // Use blend as the blend function ); } finally { // Release device context. ReleaseDC(IntPtr.Zero, screenDc); if (hBitmap != IntPtr.Zero) { SelectObject(memDc, hOldBitmap); DeleteObject(hBitmap); } DeleteDC(memDc); } } // Source: MSDN Code Gallery // https://code.msdn.microsoft.com/windowsapps/CSWinFormLayeredWindow-23cdc375 // https://stackoverflow.com/questions/33530623/c-sharp-windows-form-transparent-background-image const Int32 WS_EX_LAYERED = 0x80000; const Int32 HTCAPTION = 0x02; const Int32 WM_NCHITTEST = 0x84; const Int32 ULW_ALPHA = 0x02; const byte AC_SRC_OVER = 0x00; const byte AC_SRC_ALPHA = 0x01; [StructLayout(LayoutKind.Sequential)] struct POINT { public Int32 x; public Int32 y; public POINT(Int32 x, Int32 y) { this.x = x; this.y = y; } } [StructLayout(LayoutKind.Sequential)] struct SIZE { public Int32 cx; public Int32 cy; public SIZE(Int32 cx, Int32 cy) { this.cx = cx; this.cy = cy; } } [StructLayout(LayoutKind.Sequential, Pack = 1)] struct ARGB { public byte Blue; public byte Green; public byte Red; public byte Alpha; } [StructLayout(LayoutKind.Sequential, Pack = 1)] struct BLENDFUNCTION { public byte BlendOp; public byte BlendFlags; public byte SourceConstantAlpha; public byte AlphaFormat; } /// /// /// /// A handle to a layered window. A layered window is created by /// specifying WS_EX_LAYERED when creating the window with the CreateWindowEx /// function. /// A handle to a DC for the screen. This handle is obtained by /// specifying NULL when calling the function. It is used for palette color matching /// when the window contents are updated. If hdcDst isNULL, the default palette will /// be used. /// /// If hdcSrc is NULL, hdcDst must be NULL. /// A pointer to a structure that specifies the new screen /// position of the layered window. If the current position is not changing, /// pptDst can be NULL. /// A pointer to a structure that specifies the new size of /// the layered window. If the size of the window is not changing, psize can be /// NULL. If hdcSrc is NULL, psize must be NULL. /// A handle to a DC for the surface that defines the layered /// window. This handle can be obtained by calling the CreateCompatibleDC function. /// If the shape and visual context of the window are not changing, hdcSrc can be /// NULL. /// A pointer to a structure that specifies the location of /// the layer in the device context. If hdcSrc is NULL, pptSrc should be /// NULL. /// A structure that specifies the color key to be used when /// composing the layered window. /// A pointer to a structure that specifies the transparency /// value to be used when composing the layered window. /// This parameter can be one of the following values. /// - ULW_ALPHA: Use pblend as the blend function. If the display mode is 256 /// colors or less, the effect of this value is the same as the /// effect of ULW_OPAQUE. /// - ULW_COLORKEY: Use crKey as the transparency color. /// - ULW_OPAQUE: Draw an opaque layered window. /// - ULW_EX_NORESIZE: Force the UpdateLayeredWindowIndirect function to fail /// if the current window size does not match the size /// in the psize. /// If hdcSrc is NULL, dwFlags should be zero. /// /// /// If the function succeeds, the return value is nonzero. /// /// If the function fails, the return value is zero.To get extended error /// information, call GetLastError. /// [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst, ref POINT pptDst, ref SIZE psize, IntPtr hdcSrc, ref POINT pptSrc, Int32 crKey, ref BLENDFUNCTION pblend, Int32 dwFlags); [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)] static extern IntPtr CreateCompatibleDC(IntPtr hDC); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] static extern IntPtr GetDC(IntPtr hWnd); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC); [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool DeleteDC(IntPtr hdc); [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)] static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject); [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool DeleteObject(IntPtr hObject); } } ================================================ FILE: KeyNStroke/NativeMethodsEvents.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; namespace KeyNStroke { public class NativeMethodsEvents { [DllImport("user32.dll")] static public extern IntPtr SetWinEventHook(WinEvents eventMin, WinEvents eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, WinEventFlags dwFlags); [DllImport("user32.dll")] static public extern bool UnhookWinEvent(IntPtr hWinEventHook); public delegate void WinEventDelegate(IntPtr hWinEventHook, WinEvents eventType, IntPtr hwnd, uint idObject, uint idChild, uint dwEventThread, uint dwmsEventTime); public enum WinEventFlags : uint { WINEVENT_OUTOFCONTEXT = 0x0000, // Events are ASYNC WINEVENT_SKIPOWNTHREAD = 0x0001, // Don't call back for events on installer's thread WINEVENT_SKIPOWNPROCESS = 0x0002, // Don't call back for events on installer's process WINEVENT_INCONTEXT = 0x0004, // Events are SYNC, this causes your dll to be injected into every process } public enum WinEvents : uint { /** The range of WinEvent constant values specified by the Accessibility Interoperability Alliance (AIA) for use across the industry. * For more information, see Allocation of WinEvent IDs. */ EVENT_AIA_START = 0xA000, EVENT_AIA_END = 0xAFFF, /** The lowest and highest possible event values. */ EVENT_MIN = 0x00000001, EVENT_MAX = 0x7FFFFFFF, /** An object's KeyboardShortcut property has changed. Server applications send this event for their accessible objects. */ EVENT_OBJECT_ACCELERATORCHANGE = 0x8012, /** Sent when a window is cloaked. A cloaked window still exists, but is invisible to the user. */ EVENT_OBJECT_CLOAKED = 0x8017, /** A window object's scrolling has ended. Unlike EVENT_SYSTEM_SCROLLEND, this event is associated with the scrolling window. * Whether the scrolling is horizontal or vertical scrolling, this event should be sent whenever the scroll action is completed. * The hwnd parameter of the WinEventProc callback function describes the scrolling window; the idObject parameter is OBJID_CLIENT, * and the idChild parameter is CHILDID_SELF. */ EVENT_OBJECT_CONTENTSCROLLED = 0x8015, /** An object has been created. The system sends this event for the following user interface elements: caret, header control, * list-view control, tab control, toolbar control, tree view control, and window object. Server applications send this event * for their accessible objects. * Before sending the event for the parent object, servers must send it for all of an object's child objects. * Servers must ensure that all child objects are fully created and ready to accept IAccessible calls from clients before * the parent object sends this event. * Because a parent object is created after its child objects, clients must make sure that an object's parent has been created * before calling IAccessible::get_accParent, particularly if in-context hook functions are used. */ EVENT_OBJECT_CREATE = 0x8000, /** An object's DefaultAction property has changed. The system sends this event for dialog boxes. Server applications send * this event for their accessible objects. */ EVENT_OBJECT_DEFACTIONCHANGE = 0x8011, /** An object's Description property has changed. Server applications send this event for their accessible objects. */ EVENT_OBJECT_DESCRIPTIONCHANGE = 0x800D, /** An object has been destroyed. The system sends this event for the following user interface elements: caret, header control, * list-view control, tab control, toolbar control, tree view control, and window object. Server applications send this event for * their accessible objects. * Clients assume that all of an object's children are destroyed when the parent object sends this event. * After receiving this event, clients do not call an object's IAccessible properties or methods. However, the interface pointer * must remain valid as long as there is a reference count on it (due to COM rules), but the UI element may no longer be present. * Further calls on the interface pointer may return failure errors; to prevent this, servers create proxy objects and monitor * their life spans. */ EVENT_OBJECT_DESTROY = 0x8001, /** The user started to drag an element. The hwnd, idObject, and idChild parameters of the WinEventProc callback function * identify the object being dragged. */ EVENT_OBJECT_DRAGSTART = 0x8021, /** The user has ended a drag operation before dropping the dragged element on a drop target. The hwnd, idObject, and idChild * parameters of the WinEventProc callback function identify the object being dragged. */ EVENT_OBJECT_DRAGCANCEL = 0x8022, /** The user dropped an element on a drop target. The hwnd, idObject, and idChild parameters of the WinEventProc callback * function identify the object being dragged. */ EVENT_OBJECT_DRAGCOMPLETE = 0x8023, /** The user dragged an element into a drop target's boundary. The hwnd, idObject, and idChild parameters of the WinEventProc * callback function identify the drop target. */ EVENT_OBJECT_DRAGENTER = 0x8024, /** The user dragged an element out of a drop target's boundary. The hwnd, idObject, and idChild parameters of the WinEventProc * callback function identify the drop target. */ EVENT_OBJECT_DRAGLEAVE = 0x8025, /** The user dropped an element on a drop target. The hwnd, idObject, and idChild parameters of the WinEventProc callback * function identify the drop target. */ EVENT_OBJECT_DRAGDROPPED = 0x8026, /** The highest object event value. */ EVENT_OBJECT_END = 0x80FF, /** An object has received the keyboard focus. The system sends this event for the following user interface elements: * list-view control, menu bar, pop-up menu, switch window, tab control, tree view control, and window object. * Server applications send this event for their accessible objects. * The hwnd parameter of the WinEventProc callback function identifies the window that receives the keyboard focus. */ EVENT_OBJECT_FOCUS = 0x8005, /** An object's Help property has changed. Server applications send this event for their accessible objects. */ EVENT_OBJECT_HELPCHANGE = 0x8010, /** An object is hidden. The system sends this event for the following user interface elements: caret and cursor. * Server applications send this event for their accessible objects. * When this event is generated for a parent object, all child objects are already hidden. * Server applications do not send this event for the child objects. * Hidden objects include the STATE_SYSTEM_INVISIBLE flag; shown objects do not include this flag. The EVENT_OBJECT_HIDE event * also indicates that the STATE_SYSTEM_INVISIBLE flag is set. Therefore, servers do not send the EVENT_STATE_CHANGE event in * this case. */ EVENT_OBJECT_HIDE = 0x8003, /** A window that hosts other accessible objects has changed the hosted objects. A client might need to query the host * window to discover the new hosted objects, especially if the client has been monitoring events from the window. * A hosted object is an object from an accessibility framework (MSAA or UI Automation) that is different from that of the host. * Changes in hosted objects that are from the same framework as the host should be handed with the structural change events, * such as EVENT_OBJECT_CREATE for MSAA. For more info see comments within winuser.h. */ EVENT_OBJECT_HOSTEDOBJECTSINVALIDATED = 0x8020, /** An IME window has become hidden. */ EVENT_OBJECT_IME_HIDE = 0x8028, /** An IME window has become visible. */ EVENT_OBJECT_IME_SHOW = 0x8027, /** The size or position of an IME window has changed. */ EVENT_OBJECT_IME_CHANGE = 0x8029, /** An object has been invoked; for example, the user has clicked a button. This event is supported by common controls and is * used by UI Automation. * For this event, the hwnd, ID, and idChild parameters of the WinEventProc callback function identify the item that is invoked. */ EVENT_OBJECT_INVOKED = 0x8013, /** An object that is part of a live region has changed. A live region is an area of an application that changes frequently * and/or asynchronously. */ EVENT_OBJECT_LIVEREGIONCHANGED = 0x8019, /** An object has changed location, shape, or size. The system sends this event for the following user interface elements: * caret and window objects. Server applications send this event for their accessible objects. * This event is generated in response to a change in the top-level object within the object hierarchy; it is not generated for any * children that the object might have. For example, if the user resizes a window, the system sends this notification for the window, * but not for the menu bar, title bar, scroll bar, or other objects that have also changed. * The system does not send this event for every non-floating child window when the parent moves. However, if an application explicitly * resizes child windows as a result of resizing the parent window, the system sends multiple events for the resized children. * If an object's State property is set to STATE_SYSTEM_FLOATING, the server sends EVENT_OBJECT_LOCATIONCHANGE whenever the object changes * location. If an object does not have this state, servers only trigger this event when the object moves in relation to its parent. * For this event notification, the idChild parameter of the WinEventProc callback function identifies the child object that has changed. */ EVENT_OBJECT_LOCATIONCHANGE = 0x800B, /** An object's Name property has changed. The system sends this event for the following user interface elements: check box, * cursor, list-view control, push button, radio button, status bar control, tree view control, and window object. Server * * applications send this event for their accessible objects. */ EVENT_OBJECT_NAMECHANGE = 0x800C, /** An object has a new parent object. Server applications send this event for their accessible objects. */ EVENT_OBJECT_PARENTCHANGE = 0x800F, /** A container object has added, removed, or reordered its children. The system sends this event for the following user * interface elements: header control, list-view control, toolbar control, and window object. Server applications send this * event as appropriate for their accessible objects. * For example, this event is generated by a list-view object when the number of child elements or the order of the elements changes. * This event is also sent by a parent window when the Z-order for the child windows changes. */ EVENT_OBJECT_REORDER = 0x8004, /** The selection within a container object has changed. The system sends this event for the following user interface elements: * list-view control, tab control, tree view control, and window object. Server applications send this event for their accessible * objects. * This event signals a single selection: either a child is selected in a container that previously did not contain any selected children, * or the selection has changed from one child to another. * The hwnd and idObject parameters of the WinEventProc callback function describe the container; the idChild parameter identifies the object * that is selected. If the selected child is a window that also contains objects, the idChild parameter is OBJID_WINDOW. */ EVENT_OBJECT_SELECTION = 0x8006, /** A child within a container object has been added to an existing selection. The system sends this event for the following user * interface elements: list box, list-view control, and tree view control. Server applications send this event for their accessible * objects. * The hwnd and idObject parameters of the WinEventProc callback function describe the container. The idChild parameter is the child that * is added to the selection. */ EVENT_OBJECT_SELECTIONADD = 0x8007, /** An item within a container object has been removed from the selection. The system sends this event for the following user * interface elements: list box, list-view control, and tree view control. Server applications send this event for their accessible * objects. * This event signals that a child is removed from an existing selection. * The hwnd and idObject parameters of the WinEventProc callback function describe the container; the idChild parameter identifies * the child that has been removed from the selection. */ EVENT_OBJECT_SELECTIONREMOVE = 0x8008, /** Numerous selection changes have occurred within a container object. The system sends this event for list boxes; server * applications send it for their accessible objects. * This event is sent when the selected items within a control have changed substantially. The event informs the client * that many selection changes have occurred, and it is sent instead of several * EVENT_OBJECT_SELECTIONADD or EVENT_OBJECT_SELECTIONREMOVE events. The client * queries for the selected items by calling the container object's IAccessible::get_accSelection method and * enumerating the selected items. For this event notification, the hwnd and idObject parameters of the WinEventProc callback * function describe the container in which the changes occurred. */ EVENT_OBJECT_SELECTIONWITHIN = 0x8009, /** A hidden object is shown. The system sends this event for the following user interface elements: caret, cursor, and window * object. Server applications send this event for their accessible objects. * Clients assume that when this event is sent by a parent object, all child objects are already displayed. * Therefore, server applications do not send this event for the child objects. * Hidden objects include the STATE_SYSTEM_INVISIBLE flag; shown objects do not include this flag. * The EVENT_OBJECT_SHOW event also indicates that the STATE_SYSTEM_INVISIBLE flag is cleared. Therefore, servers * do not send the EVENT_STATE_CHANGE event in this case. */ EVENT_OBJECT_SHOW = 0x8002, /** An object's state has changed. The system sends this event for the following user interface elements: check box, combo box, * header control, push button, radio button, scroll bar, toolbar control, tree view control, up-down control, and window object. * Server applications send this event for their accessible objects. * For example, a state change occurs when a button object is clicked or released, or when an object is enabled or disabled. * For this event notification, the idChild parameter of the WinEventProc callback function identifies the child object whose state has changed. */ EVENT_OBJECT_STATECHANGE = 0x800A, /** The conversion target within an IME composition has changed. The conversion target is the subset of the IME composition * which is actively selected as the target for user-initiated conversions. */ EVENT_OBJECT_TEXTEDIT_CONVERSIONTARGETCHANGED = 0x8030, /** An object's text selection has changed. This event is supported by common controls and is used by UI Automation. * The hwnd, ID, and idChild parameters of the WinEventProc callback function describe the item that is contained in the updated text selection. */ EVENT_OBJECT_TEXTSELECTIONCHANGED = 0x8014, /** Sent when a window is uncloaked. A cloaked window still exists, but is invisible to the user. */ EVENT_OBJECT_UNCLOAKED = 0x8018, /** An object's Value property has changed. The system sends this event for the user interface elements that include the scroll * bar and the following controls: edit, header, hot key, progress bar, slider, and up-down. Server applications send this event * for their accessible objects. */ EVENT_OBJECT_VALUECHANGE = 0x800E, /** The range of event constant values reserved for OEMs. For more information, see Allocation of WinEvent IDs. */ EVENT_OEM_DEFINED_START = 0x0101, EVENT_OEM_DEFINED_END = 0x01FF, /** An alert has been generated. Server applications should not send this event. */ EVENT_SYSTEM_ALERT = 0x0002, /** A preview rectangle is being displayed. */ EVENT_SYSTEM_ARRANGMENTPREVIEW = 0x8016, /** A window has lost mouse capture. This event is sent by the system, never by servers. */ EVENT_SYSTEM_CAPTUREEND = 0x0009, /** A window has received mouse capture. This event is sent by the system, never by servers. */ EVENT_SYSTEM_CAPTURESTART = 0x0008, /** A window has exited context-sensitive Help mode. This event is not sent consistently by the system. */ EVENT_SYSTEM_CONTEXTHELPEND = 0x000D, /** A window has entered context-sensitive Help mode. This event is not sent consistently by the system. */ EVENT_SYSTEM_CONTEXTHELPSTART = 0x000C, /** The active desktop has been switched. */ EVENT_SYSTEM_DESKTOPSWITCH = 0x0020, /** A dialog box has been closed. The system sends this event for standard dialog boxes; servers send it for custom dialog boxes. * This event is not sent consistently by the system. */ EVENT_SYSTEM_DIALOGEND = 0x0011, /** A dialog box has been displayed. The system sends this event for standard dialog boxes, which are created using resource * templates or Win32 dialog box functions. Servers send this event for custom dialog boxes, which are windows that function as * dialog boxes but are not created in the standard way. * This event is not sent consistently by the system. */ EVENT_SYSTEM_DIALOGSTART = 0x0010, /** An application is about to exit drag-and-drop mode. Applications that support drag-and-drop operations must send this event; * the system does not send this event. */ EVENT_SYSTEM_DRAGDROPEND = 0x000F, /** An application is about to enter drag-and-drop mode. Applications that support drag-and-drop operations must send this * event because the system does not send it. */ EVENT_SYSTEM_DRAGDROPSTART = 0x000E, /** The highest system event value. */ EVENT_SYSTEM_END = 0x00FF, /** The foreground window has changed. The system sends this event even if the foreground window has changed to another window * in the same thread. Server applications never send this event. * For this event, the WinEventProc callback function's hwnd parameter is the handle to the window that is in the * foreground, the idObject parameter is OBJID_WINDOW, and the idChild parameter is CHILDID_SELF. */ EVENT_SYSTEM_FOREGROUND = 0x0003, /** A pop-up menu has been closed. The system sends this event for standard menus; servers send it for custom menus. * When a pop-up menu is closed, the client receives this message, and then the EVENT_SYSTEM_MENUEND event. * This event is not sent consistently by the system. */ EVENT_SYSTEM_MENUPOPUPEND = 0x0007, /** A pop-up menu has been displayed. The system sends this event for standard menus, which are identified by HMENU, and are * created using menu-template resources or Win32 menu functions. Servers send this event for custom menus, which are user * interface elements that function as menus but are not created in the standard way. This event is not sent consistently by the system. */ EVENT_SYSTEM_MENUPOPUPSTART = 0x0006, /** A menu from the menu bar has been closed. The system sends this event for standard menus; servers send it for custom menus. * For this event, the WinEventProc callback function's hwnd, idObject, and idChild parameters refer to the control * that contains the menu bar or the control that activates the context menu. The hwnd parameter is the handle to the window * that is related to the event. The idObject parameter is OBJID_MENU or OBJID_SYSMENU for a menu, or OBJID_WINDOW for a * pop-up menu. The idChild parameter is CHILDID_SELF. */ EVENT_SYSTEM_MENUEND = 0x0005, /** A menu item on the menu bar has been selected. The system sends this event for standard menus, which are identified * by HMENU, created using menu-template resources or Win32 menu API elements. Servers send this event for custom menus, * which are user interface elements that function as menus but are not created in the standard way. * For this event, the WinEventProc callback function's hwnd, idObject, and idChild parameters refer to the control * that contains the menu bar or the control that activates the context menu. The hwnd parameter is the handle to the window * related to the event. The idObject parameter is OBJID_MENU or OBJID_SYSMENU for a menu, or OBJID_WINDOW for a pop-up menu. * The idChild parameter is CHILDID_SELF.The system triggers more than one EVENT_SYSTEM_MENUSTART event that does not always * correspond with the EVENT_SYSTEM_MENUEND event. */ EVENT_SYSTEM_MENUSTART = 0x0004, /** A window object is about to be restored. This event is sent by the system, never by servers. */ EVENT_SYSTEM_MINIMIZEEND = 0x0017, /** A window object is about to be minimized. This event is sent by the system, never by servers. */ EVENT_SYSTEM_MINIMIZESTART = 0x0016, /** The movement or resizing of a window has finished. This event is sent by the system, never by servers. */ EVENT_SYSTEM_MOVESIZEEND = 0x000B, /** A window is being moved or resized. This event is sent by the system, never by servers. */ EVENT_SYSTEM_MOVESIZESTART = 0x000A, /** Scrolling has ended on a scroll bar. This event is sent by the system for standard scroll bar controls and for * scroll bars that are attached to a window. Servers send this event for custom scroll bars, which are user interface * elements that function as scroll bars but are not created in the standard way. * The idObject parameter that is sent to the WinEventProc callback function is OBJID_HSCROLL for horizontal scroll bars, and * OBJID_VSCROLL for vertical scroll bars. */ EVENT_SYSTEM_SCROLLINGEND = 0x0013, /** Scrolling has started on a scroll bar. The system sends this event for standard scroll bar controls and for scroll * bars attached to a window. Servers send this event for custom scroll bars, which are user interface elements that * function as scroll bars but are not created in the standard way. * The idObject parameter that is sent to the WinEventProc callback function is OBJID_HSCROLL for horizontal scrolls bars, * and OBJID_VSCROLL for vertical scroll bars. */ EVENT_SYSTEM_SCROLLINGSTART = 0x0012, /** A sound has been played. The system sends this event when a system sound, such as one for a menu, * is played even if no sound is audible (for example, due to the lack of a sound file or a sound card). * Servers send this event whenever a custom UI element generates a sound. * For this event, the WinEventProc callback function receives the OBJID_SOUND value as the idObject parameter. */ EVENT_SYSTEM_SOUND = 0x0001, /** The user has released ALT+TAB. This event is sent by the system, never by servers. * The hwnd parameter of the WinEventProc callback function identifies the window to which the user has switched. * If only one application is running when the user presses ALT+TAB, the system sends this event without a corresponding * EVENT_SYSTEM_SWITCHSTART event. */ EVENT_SYSTEM_SWITCHEND = 0x0015, /** The user has pressed ALT+TAB, which activates the switch window. This event is sent by the system, never by servers. * The hwnd parameter of the WinEventProc callback function identifies the window to which the user is switching. * If only one application is running when the user presses ALT+TAB, the system sends an EVENT_SYSTEM_SWITCHEND event without a * corresponding EVENT_SYSTEM_SWITCHSTART event. */ EVENT_SYSTEM_SWITCHSTART = 0x0014, /** The range of event constant values reserved for UI Automation event identifiers. For more information, * see Allocation of WinEvent IDs. */ EVENT_UIA_EVENTID_START = 0x4E00, EVENT_UIA_EVENTID_END = 0x4EFF, /** * The range of event constant values reserved for UI Automation property-changed event identifiers. * For more information, see Allocation of WinEvent IDs. */ EVENT_UIA_PROPID_START = 0x7500, EVENT_UIA_PROPID_END = 0x75FF } public enum ObjIds : uint { OBJID_WINDOW = 0x00000000, OBJID_SYSMENU = 0xFFFFFFFF, OBJID_TITLEBAR = 0xFFFFFFFE, OBJID_MENU = 0xFFFFFFFD, OBJID_CLIENT = 0xFFFFFFFC, OBJID_VSCROLL = 0xFFFFFFFB, OBJID_HSCROLL = 0xFFFFFFFA, OBJID_SIZEGRIP = 0xFFFFFFF9, OBJID_CARET = 0xFFFFFFF8, OBJID_CURSOR = 0xFFFFFFF7, OBJID_ALERT = 0xFFFFFFF6, OBJID_SOUND = 0xFFFFFFF5, OBJID_QUERYCLASSNAMEIDX = 0xFFFFFFF4, OBJID_NATIVEOM = 0xFFFFFFF0 } public const uint CHILDID_SELF = 0; } } ================================================ FILE: KeyNStroke/NativeMethodsGWL.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Runtime.InteropServices; namespace KeyNStroke { /// /// Each Window has some associated memory with holds flags for window behaviour /// The bytes that hold these informations are numbered as nIndex with the values GWL.GWL_**** /// These Methods can be used to define a click through behaviour /// public class NativeMethodsGWL { /// /// Activate Click Through /// /// The Forms myform.Handle public static void ClickThrough(IntPtr Handle) { uint current_val = GetWindowLong(Handle, GWL.GWL_EXSTYLE); uint changed_val = Convert.ToUInt32(current_val | (uint)WindowStyles.WS_EX_LAYERED | (uint)WindowStyles.WS_EX_TRANSPARENT); SetWindowLong(Handle, GWL.GWL_EXSTYLE, changed_val); } /// /// Deactivate Click Through /// /// The Forms myform.Handle public static void CatchClicks(IntPtr Handle) { uint current_val = GetWindowLong(Handle, GWL.GWL_EXSTYLE); uint changed_val = Convert.ToUInt32(current_val & ~((uint)WindowStyles.WS_EX_LAYERED)) & ~((uint)WindowStyles.WS_EX_TRANSPARENT); SetWindowLong(Handle, GWL.GWL_EXSTYLE, changed_val); } public static void HideFromAltTab(IntPtr Handle) { int current_val = (int)GetWindowLong(Handle, GWL.GWL_EXSTYLE); uint changed_val = Convert.ToUInt32(current_val | (uint)WindowStyles.WS_EX_TOOLWINDOW); SetWindowLong(Handle, GWL.GWL_EXSTYLE, changed_val); } /// /// These are the possible longs which can be get and set via GetWindowLong and ... /// public enum GWL : int { GWL_WNDPROC = (-4), GWL_HINSTANCE = (-6), GWL_HWNDPARENT = (-8), GWL_STYLE = (-16), GWL_EXSTYLE = (-20), GWL_USERDATA = (-21), GWL_ID = (-12) } /// /// These are the Flags for the Extended Style Bytes (nIndex=GWL_EXTSTYLE) /// [Flags] public enum WindowStyles : uint { //Extended Window Styles WS_EX_DLGMODALFRAME = 0x00000001, WS_EX_NOPARENTNOTIFY = 0x00000004, WS_EX_TOPMOST = 0x00000008, WS_EX_ACCEPTFILES = 0x00000010, WS_EX_TRANSPARENT = 0x00000020, WS_EX_LAYERED = 0x00080000, WS_EX_TOOLWINDOW = 0x00000080, } // Choose 32/64 bit variant of function /// /// returns long bytes at nIndex /// /// /// /// the LONG value public static uint GetWindowLong(IntPtr hWnd, GWL nIndex) { if (IntPtr.Size == 4) { return (uint)GetWindowLong32(hWnd, (int)nIndex); } return (uint)GetWindowLongPtr64(hWnd, (int)nIndex); } /// /// sets the LONG value at nIndex to nwNewLong /// /// /// /// /// dunno public static int SetWindowLong(IntPtr hWnd, GWL nIndex, uint dwNewLong) { if (IntPtr.Size == 4) { return SetWindowLong32(hWnd, (int)nIndex, dwNewLong); } return SetWindowLongPtr64(hWnd, (int)nIndex, dwNewLong); } // Different Functions for Win32 and 64 bit [DllImport("user32.dll", EntryPoint = "GetWindowLong", CharSet = CharSet.Auto)] private static extern int GetWindowLong32(IntPtr hWnd, int nIndex); [DllImport("user32.dll", EntryPoint = "GetWindowLongPtr", CharSet = CharSet.Auto)] private static extern int GetWindowLongPtr64(IntPtr hWnd, int nIndex); [DllImport("user32.dll", EntryPoint = "SetWindowLong", CharSet = CharSet.Auto)] private static extern int SetWindowLong32(IntPtr hWnd, int nIndex, uint dwNewLong); [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr", CharSet = CharSet.Auto)] private static extern int SetWindowLongPtr64(IntPtr hWnd, int nIndex, uint dwNewLong); } } ================================================ FILE: KeyNStroke/NativeMethodsKeyboard.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; namespace KeyNStroke { [ComVisibleAttribute(false), System.Security.SuppressUnmanagedCodeSecurity()] public class NativeMethodsKeyboard { // Structure returned by the hook whenever a key is pressed [StructLayout(LayoutKind.Sequential)] public struct KBDLLHOOKSTRUCT { public int vkCode; public int scanCode; public int flags; public int time; public int dwExtraInfo; } public delegate IntPtr HookHandlerDelegate(int nCode, IntPtr wParam, ref KBDLLHOOKSTRUCT lParam); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern IntPtr GetModuleHandle(string lpModuleName); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern IntPtr SetWindowsHookEx(int idHook, HookHandlerDelegate lpfn, IntPtr hMod, uint dwThreadId); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool UnhookWindowsHookEx(IntPtr hhk); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, ref KBDLLHOOKSTRUCT lParam); [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)] public static extern UInt16 GetKeyState(int keyCode); [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] public static extern int GetKeyNameText(int lParam, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder lpString, int nSize); [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern uint MapVirtualKey(uint uCode, uint uMapType); public const uint MAPVK_VK_TO_CHAR = 2; /// /// The ToAscii function translates the specified virtual-key code and keyboard /// state to the corresponding character or characters. The function translates the code /// using the input language and physical keyboard layout identified by the keyboard layout handle. /// /// /// [in] Specifies the virtual-key code to be translated. /// /// /// [in] Specifies the hardware scan code of the key to be translated. /// The high-order bit of this value is set if the key is up (not pressed). /// /// /// [in] Pointer to a 256-byte array that contains the current keyboard state. /// Each element (byte) in the array contains the state of one key. /// If the high-order bit of a byte is set, the key is down (pressed). /// The low bit, if set, indicates that the key is toggled on. In this function, /// only the toggle bit of the CAPS LOCK key is relevant. The toggle state /// of the NUM LOCK and SCROLL LOCK keys is ignored. /// /// /// [out] Pointer to the buffer that receives the translated character or characters. /// /// /// [in] Specifies whether a menu is active. This parameter must be 1 if a menu is active, or 0 otherwise. /// /// /// If the specified key is a dead key, the return value is negative. Otherwise, it is one of the following values. /// Value Meaning /// 0 The specified virtual key has no translation for the current state of the keyboard. /// 1 One character was copied to the buffer. /// 2 Two characters were copied to the buffer. This usually happens when a dead-key character /// (accent or diacritic) stored in the keyboard layout cannot be composed with the specified /// virtual key to form a single character. /// /// /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/userinput/keyboardinput/keyboardinputreference/keyboardinputfunctions/toascii.asp /// [DllImport("user32")] public static extern int ToAscii( int uVirtKey, int uScanCode, byte[] lpbKeyState, byte[] lpwTransKey, int fuState); /* int WINAPI ToAscii( _In_ UINT uVirtKey, _In_ UINT uScanCode, _In_opt_ const BYTE *lpKeyState, _Out_ LPWORD lpChar, _In_ UINT uFlags );*/ [DllImport("user32", SetLastError = true, CharSet = CharSet.Unicode)] public static extern int ToUnicode( int uVirtKey, int uScanCode, byte[] lpbKeyState, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder pwszBuff, int cchBuff, uint wFlags); /* int WINAPI ToUnicode( _In_ UINT wVirtKey, _In_ UINT wScanCode, _In_opt_ const BYTE *lpKeyState, _Out_ LPWSTR pwszBuff, _In_ int cchBuff, _In_ UINT wFlags );*/ /// /// The GetKeyboardState function copies the status of the 256 virtual keys to the /// specified buffer. /// /// /// [in] Pointer to a 256-byte array that contains keyboard key states. /// /// /// If the function succeeds, the return value is nonzero. /// If the function fails, the return value is zero. To get extended error information, call GetLastError. /// /// /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/userinput/keyboardinput/keyboardinputreference/keyboardinputfunctions/toascii.asp /// [DllImport("user32")] public static extern int GetKeyboardState(byte[] pbKeyState); } } ================================================ FILE: KeyNStroke/NativeMethodsMouse.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Runtime.InteropServices; namespace KeyNStroke { [ComVisibleAttribute(false), System.Security.SuppressUnmanagedCodeSecurity()] public class NativeMethodsMouse { public const int WM_MOUSEMOVE = 0x200; public const int WM_LBUTTONDOWN = 0x201; public const int WM_LBUTTONUP = 0x202; public const int WM_LBUTTONDBLCLK = 0x203; public const int WM_RBUTTONDOWN = 0x204; public const int WM_RBUTTONUP = 0x205; public const int WM_RBUTTONDBLCLK = 0x206; public const int WM_MBUTTONDOWN = 0x207; public const int WM_MBUTTONUP = 0x208; public const int WM_MBUTTONDBLCLK = 0x209; public const int WM_MOUSEWHEEL1 = 0x20A; public const int WM_XBUTTONDOWN = 0x20B; public const int WM_XBUTTONUP = 0x20C; public const int WM_XBUTTONDBLCLK = 0x20D; public const int WM_MOUSEHWHEEL2 = 0x20E; [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern IntPtr SetWindowsHookEx(int idHook, HookHandlerDelegate lpfn, IntPtr hMod, uint dwThreadId); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool UnhookWindowsHookEx(IntPtr hhk); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, UIntPtr wParam, ref MSLLHOOKSTRUCT lParam); [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] public static extern int GetDoubleClickTime(); public const int WH_MOUSE_LL = 14; public const int HC_ACTION = 0; // The wParam and lParam parameters contain information about a mouse message. [StructLayout(LayoutKind.Sequential)] public struct POINT { public Int32 X; public Int32 Y; public POINT(int x, int y) { this.X = x; this.Y = y; } public POINT(System.Drawing.Point pt) : this(pt.X, pt.Y) { } public static implicit operator System.Drawing.Point(POINT p) { return new System.Drawing.Point(p.X, p.Y); } public static implicit operator POINT(System.Drawing.Point p) { return new POINT(p.X, p.Y); } public static implicit operator System.Windows.Point(POINT p) { return new System.Windows.Point(p.X, p.Y); } public bool Equals(POINT p2) { return X == p2.X && Y == p2.Y; } } [StructLayout(LayoutKind.Sequential)] public struct MSLLHOOKSTRUCT { public POINT pt; public UInt32 mouseData; // be careful, this must be ints, not uints (was wrong before I changed it...). regards, cmew. public UInt32 flags; public UInt32 time; public UIntPtr dwExtraInfo; } public delegate IntPtr HookHandlerDelegate(int nCode, UIntPtr wParam, ref MSLLHOOKSTRUCT lParam); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern bool GetCursorPos(ref NativeMethodsMouse.POINT lpPoint); public static POINT CursorPosition { get { POINT pos = new POINT(0, 0); GetCursorPos(ref pos); return pos; } } [StructLayout(LayoutKind.Sequential)] public struct CURSORINFO { public UInt32 cbSize; public UInt32 flags; public UIntPtr hCursor; public POINT ptScreenPos; } public const UInt32 CURSOR_HIDDEN = 0x00000000; public const UInt32 CURSOR_SHOWING = 0x00000001; public const UInt32 CURSOR_SUPPRESSED = 0x00000002; [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern bool GetCursorInfo(ref CURSORINFO pci); public static CURSORINFO GetCursorInfoWrapper() { CURSORINFO info = new CURSORINFO(); info.cbSize = (uint)System.Runtime.InteropServices.Marshal.SizeOf(); GetCursorInfo(ref info); return info; } } } ================================================ FILE: KeyNStroke/NativeMethodsWindow.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; namespace KeyNStroke { public class NativeMethodsWindow { public const UInt32 S_OK = 0x00000000; /// /// Sets the Topmost property for the window Handle /// /// The Forms myform.Handle public static void SetWindowTopMost(IntPtr Handle) { SetWindowPos(Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); } /// /// Sets the Position property for the window Handle /// /// The Forms myform.Handle public static void SetWindowPosition(IntPtr Handle, int x, int y) { SetWindowPos(Handle, HWND_TOPMOST, x, y, 0, 0, SWP_NOSIZE | SWP_NOOWNERZORDER | SWP_NOACTIVATE); } /// /// Sets the Size property for the window Handle /// /// The Forms myform.Handle public static void SetWindowSize(IntPtr Handle, int width, int height) { SetWindowPos(Handle, HWND_TOPMOST, 0, 0, width, height, SWP_NOMOVE | SWP_NOOWNERZORDER); } public static readonly IntPtr HWND_TOPMOST = new IntPtr(-1); public static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2); public static readonly IntPtr HWND_TOP = new IntPtr(0); public static readonly IntPtr HWND_BOTTOM = new IntPtr(1); public const UInt32 SWP_NOSIZE = 0x0001; public const UInt32 SWP_NOMOVE = 0x0002; public const UInt32 SWP_NOACTIVATE = 0x0010; public const UInt32 SWP_NOOWNERZORDER = 0x0200; [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); public enum MonitorOptions : uint { MONITOR_DEFAULTTONULL = 0x00000000, MONITOR_DEFAULTTOPRIMARY = 0x00000001, MONITOR_DEFAULTTONEAREST = 0x00000002 } [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern IntPtr MonitorFromPoint(NativeMethodsMouse.POINT pt, MonitorOptions dwFlags); public enum DpiType : uint { MDT_EFFECTIVE_DPI = 0, MDT_ANGULAR_DPI = 1, MDT_RAW_DPI = 2, MDT_DEFAULT = 3 } [DllImport("Shcore.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern uint GetDpiForMonitor(IntPtr hmonitor, DpiType dpiType, ref uint dpiX, ref uint dpiY); public enum ProcessDpiAwareness : uint { PROCESS_DPI_UNAWARE = 0, PROCESS_SYSTEM_DPI_AWARE = 1, PROCESS_PER_MONITOR_DPI_AWARE = 2 }; [DllImport("Shcore.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern uint GetProcessDpiAwareness(IntPtr hprocess, ref ProcessDpiAwareness value); /*#define DPI_AWARENESS_CONTEXT_UNAWARE ((DPI_AWARENESS_CONTEXT)-1) #define DPI_AWARENESS_CONTEXT_SYSTEM_AWARE ((DPI_AWARENESS_CONTEXT)-2) #define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE ((DPI_AWARENESS_CONTEXT)-3) #define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT)-4) #define DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED ((DPI_AWARENESS_CONTEXT)-5)*/ [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern IntPtr GetThreadDpiAwarenessContext(); public enum DpiAwareness : uint { DPI_AWARENESS_INVALID, DPI_AWARENESS_UNAWARE, DPI_AWARENESS_SYSTEM_AWARE, DPI_AWARENESS_PER_MONITOR_AWARE }; [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern DpiAwareness GetAwarenessFromDpiAwarenessContext(IntPtr hDpiAwarenessContext); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern uint GetDpiFromDpiAwarenessContext(IntPtr hDpiAwarenessContext); [DllImport("user32.dll", CharSet = CharSet.Auto)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, EnumMonitorsDelegate lpfnEnum, IntPtr dwData); delegate bool EnumMonitorsDelegate(IntPtr hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData); [StructLayout(LayoutKind.Sequential)] public struct Rect { public int left; public int top; public int right; public int bottom; } static public List GetAllUsedDpis() { List dpis = new List(); EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, delegate (IntPtr hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData) { uint adpiX = 0, adpiY = 0; uint result = GetDpiForMonitor(hMonitor, DpiType.MDT_ANGULAR_DPI, ref adpiX, ref adpiY); if (result == S_OK) { dpis.Add(adpiX); } return true; // continue enumeration }, IntPtr.Zero); return dpis; } static public void PrintDpiAwarenessInfo() { NativeMethodsMouse.POINT cursorPosition = new NativeMethodsMouse.POINT(0, 0); NativeMethodsMouse.GetCursorPos(ref cursorPosition); IntPtr monitor = MonitorFromPoint(cursorPosition, MonitorOptions.MONITOR_DEFAULTTONEAREST); // Angular DPI seems to work best uint edpiX = 0, edpiY = 0; NativeMethodsWindow.GetDpiForMonitor(monitor, NativeMethodsWindow.DpiType.MDT_EFFECTIVE_DPI, ref edpiX, ref edpiY); uint adpiX = 0, adpiY = 0; NativeMethodsWindow.GetDpiForMonitor(monitor, NativeMethodsWindow.DpiType.MDT_ANGULAR_DPI, ref adpiX, ref adpiY); uint rdpiX = 0, rdpiY = 0; NativeMethodsWindow.GetDpiForMonitor(monitor, NativeMethodsWindow.DpiType.MDT_RAW_DPI, ref rdpiX, ref rdpiY); ProcessDpiAwareness dpiawareness = 0; GetProcessDpiAwareness(IntPtr.Zero, ref dpiawareness); IntPtr hContext = GetThreadDpiAwarenessContext(); DpiAwareness threaddpiawareness = GetAwarenessFromDpiAwarenessContext(hContext); uint threadDpi = GetDpiFromDpiAwarenessContext(hContext); Log.e("DPI", $"DPI: {edpiX},{edpiY}/{adpiX},{adpiY}/{rdpiX},{rdpiY}, Monitor: {(int)monitor}, Awareness: {dpiawareness}/{threaddpiawareness}/{threadDpi}"); } } } ================================================ FILE: KeyNStroke/Properties/AssemblyInfo.cs ================================================ using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Windows; // Allgemeine Informationen über eine Assembly werden über die folgenden // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, // die einer Assembly zugeordnet sind. [assembly: AssemblyTitle("Key'n'Stroke")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Key'n'Stroke")] [assembly: AssemblyCopyright("Copyright © 2021")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] // Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly // für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von // COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. [assembly: ComVisible(false)] //Um mit dem Erstellen lokalisierbarer Anwendungen zu beginnen, legen Sie //ImCodeVerwendeteKultur in der .csproj-Datei //in einer fest. Wenn Sie in den Quelldateien beispielsweise Deutsch //(Deutschland) verwenden, legen Sie auf \"de-DE\" fest. Heben Sie dann die Auskommentierung //des nachstehenden NeutralResourceLanguage-Attributs auf. Aktualisieren Sie "en-US" in der nachstehenden Zeile, //sodass es mit der UICulture-Einstellung in der Projektdatei übereinstimmt. //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] [assembly: ThemeInfo( ResourceDictionaryLocation.None, //Speicherort der designspezifischen Ressourcenwörterbücher //(wird verwendet, wenn eine Ressource auf der Seite nicht gefunden wird, // oder in den Anwendungsressourcen-Wörterbüchern nicht gefunden werden kann.) ResourceDictionaryLocation.SourceAssembly //Speicherort des generischen Ressourcenwörterbuchs //(wird verwendet, wenn eine Ressource auf der Seite nicht gefunden wird, // designspezifischen Ressourcenwörterbuch nicht gefunden werden kann.) )] // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: // // Hauptversion // Nebenversion // Buildnummer // Revision // // Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden, // übernehmen, indem Sie "*" eingeben: // [assembly: AssemblyVersion("1.0.*")] //[assembly: AssemblyVersion("1.2.0.0")] //[assembly: AssemblyFileVersion("1.2.0.0")] ================================================ FILE: KeyNStroke/Properties/Resources.Designer.cs ================================================ //------------------------------------------------------------------------------ // // Dieser Code wurde von einem Tool generiert. // Laufzeitversion:4.0.30319.42000 // // Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn // der Code erneut generiert wird. // //------------------------------------------------------------------------------ namespace KeyNStroke.Properties { using System; /// /// Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw. /// // Diese Klasse wurde von der StronglyTypedResourceBuilder automatisch generiert // -Klasse über ein Tool wie ResGen oder Visual Studio automatisch generiert. // Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen // mit der /str-Option erneut aus, oder Sie erstellen Ihr VS-Projekt neu. [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal Resources() { } /// /// Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("KeyNStroke.Properties.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; } } /// /// Überschreibt die CurrentUICulture-Eigenschaft des aktuellen Threads für alle /// Ressourcenzuordnungen, die diese stark typisierte Ressourcenklasse verwenden. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } set { resourceCulture = value; } } } } ================================================ FILE: KeyNStroke/Properties/Resources.resx ================================================  text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 ================================================ FILE: KeyNStroke/Properties/Settings.Designer.cs ================================================ //------------------------------------------------------------------------------ // // Dieser Code wurde von einem Tool generiert. // Laufzeitversion:4.0.30319.42000 // // Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn // der Code erneut generiert wird. // //------------------------------------------------------------------------------ namespace KeyNStroke.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.5.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); public static Settings Default { get { return defaultInstance; } } } } ================================================ FILE: KeyNStroke/Properties/Settings.settings ================================================  ================================================ FILE: KeyNStroke/ReadShortcut.xaml ================================================  Press shortcut for Set the window opacity via the "A" channel of the background color. Lines disappear after s Hide window when empty Put on top periodically Resize/move mode Change       Trigger Reset to Passwort protection mode Change       Trigger Reset to Indicate cursor position Change color on click Draw circle edge Show modifiers (Ctrl, Shift, ...) Use custom icons. Currently using built-in icons. Currently using icons from Refresh cache Enable Line drawing How-To use: Press the 'Trigger Line Draw' shortcut (). Then click and drag to draw a line. Click again to clear the line. The shortcut needs to be pressed again for the next line. Trigger Line Draw Change       Trigger Reset to Standby mode In standby mode, all Key'n'Stroke functionality will be temporarily disabled, even if it is enabled in the respective settings. Startup in standby Show welcome window on startup. Use the update button in the welcome window to search for updates. Toggle standby mode Change       Trigger Reset to Disable Standby Mode! ================================================ FILE: KeyNStroke/Settings1.xaml.cs ================================================ using System; using System.Collections.Generic; using System.Globalization; 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; using WpfColorFontDialog; namespace KeyNStroke { public class EnumBooleanConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { //MessageBox.Show("Convert " + value.ToString() + " tt: " + targetType.ToString() + " par " + parameter.ToString()); return value.Equals(parameter); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { //MessageBox.Show("Convert Back " + value.ToString() + " tt: " + targetType.ToString() + " par " + parameter.ToString()); return ((bool)value) ? parameter : Binding.DoNothing; } } public class FloatPercentageConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { //Log.e("CNV", "Convert " + value.ToString() + " tt: " + targetType.ToString()); return (int) ((float) value * 100f); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { //Log.e("CNV", "Convert Back " + value.ToString() + " tt: " + targetType.ToString()); return (int)value / 100f; } } public class MediaColorDrawingColorConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { //Log.e("CNV", "Convert " + value.ToString() + " tt: " + targetType.ToString()); return UIHelper.ToMediaColor((System.Drawing.Color) value); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { //Log.e("CNV", "Convert Back " + value.ToString() + " tt: " + targetType.ToString()); return UIHelper.ToDrawingColor((System.Windows.Media.Color) value); } } /// /// Interaktionslogik für Settings1.xaml /// public partial class Settings1 : Window { public Settings1(SettingsStore s, IKeystrokeEventProvider k) { InitializeComponent(); settings = s; this.k = k; Log.e("BIN", "Set Data context in settings window"); layout_root.DataContext = settings; SettingsModeShortcutDefault.Text = settings.KeystrokeHistorySettingsModeShortcutDefault; PasswordModeShortcutDefault.Text = settings.KeystrokeHistoryPasswordModeShortcutDefault; AnnotateLineShortcutDefault.Text = settings.AnnotateLineShortcutDefault; StandbyShortcutDefault.Text = settings.StandbyShortcutDefault; s.PropertyChanged += S_PropertyChanged; s.CallPropertyChangedForAllProperties(); } SettingsStore settings; IKeystrokeEventProvider k; protected override void OnClosed(EventArgs e) { base.OnClosed(e); ((App)Application.Current).onSettingsWindowClosed(); } private void RadioButton_Checked(object sender, RoutedEventArgs e) { } private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e) { UrlOpener.OpenGithub(); } private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { settings.SaveAll(); } private void Bn_reset_position_Click(object sender, RoutedEventArgs e) { settings.PanelLocation = settings.PanelLocationDefault; settings.PanelSize = settings.PanelSizeDefault; settings.WindowLocation = settings.WindowLocationDefault; settings.WindowSize = settings.WindowSizeDefault; } private void bn_reset_all_Click(object sender, RoutedEventArgs e) { settings.ResetAll(); } private void Button_close_Click(object sender, RoutedEventArgs e) { Close(); } private void button_exit_Click(object sender, RoutedEventArgs e) { System.Windows.Application.Current.Shutdown(); } private void OnButtonTextFontClick(object sender, RoutedEventArgs e) { ColorFontDialog dialog = new ColorFontDialog(true, true, false); dialog.Font = settings.LabelFont; dialog.Loaded += FontDialogLoaded; if (dialog.ShowDialog() == true) { FontInfo font = dialog.Font; if (font != null) { settings.LabelFont = font; } } } private void FontDialogLoaded(object sender, RoutedEventArgs e) { // run after the init funtions of the colordialog itself ColorFontDialog dialog = (ColorFontDialog)sender; TextBox sampleText = UIHelper.FindChild((DependencyObject)dialog.Content, "txtSampleText"); sampleText.Background = new SolidColorBrush(UIHelper.ToMediaColor(settings.BackgroundColor)); sampleText.Foreground = new SolidColorBrush(UIHelper.ToMediaColor(settings.LabelColor)); } #region Shortcut - ChangeResizeMove private void Hyperlink_ChangeResizeMoveShortcut(object sender, RoutedEventArgs e) { ReadShortcut rs = new ReadShortcut(k, "enabling and disabling the move+resize mode for the keystroke history window."); rs.ShowDialog(); if (rs.Shortcut != null) { settings.KeystrokeHistorySettingsModeShortcut = rs.Shortcut; } } private void Hyperlink_ResetResizeMoveShortcut(object sender, RoutedEventArgs e) { settings.KeystrokeHistorySettingsModeShortcut = settings.KeystrokeHistorySettingsModeShortcutDefault; } private void Hyperlink_TriggerResizeMoveShortcut(object sender, RoutedEventArgs e) { settings.EnableSettingsMode = !settings.EnableSettingsMode; } #endregion #region Shortcut - PasswordMode private void Hyperlink_ChangePasswordModeShortcut(object sender, RoutedEventArgs e) { ReadShortcut rs = new ReadShortcut(k, " toggling password protection mode."); rs.ShowDialog(); if (rs.Shortcut != null) { settings.KeystrokeHistoryPasswordModeShortcut= rs.Shortcut; } } private void Hyperlink_ResetPasswordModeShortcut(object sender, RoutedEventArgs e) { settings.KeystrokeHistoryPasswordModeShortcut = settings.KeystrokeHistoryPasswordModeShortcutDefault; } private void Hyperlink_TriggerPasswordModeShortcut(object sender, RoutedEventArgs e) { settings.EnablePasswordMode = !settings.EnablePasswordMode; } #endregion #region Shortcut - AnnotateLine private void Hyperlink_ChangeAnnotateLineShortcut(object sender, RoutedEventArgs e) { ReadShortcut rs = new ReadShortcut(k, " triggering line draw."); rs.ShowDialog(); if (rs.Shortcut != null) { settings.AnnotateLineShortcut = rs.Shortcut; } } private void Hyperlink_ResetAnnotateLineShortcut(object sender, RoutedEventArgs e) { settings.AnnotateLineShortcut = settings.AnnotateLineShortcutDefault; } private void Hyperlink_TriggerAnnotateLineShortcut(object sender, RoutedEventArgs e) { settings.AnnotateLineShortcutTrigger = true; } #endregion #region Shortcut - Toggle Standby private void Hyperlink_ChangeStandbyShortcut(object sender, RoutedEventArgs e) { ReadShortcut rs = new ReadShortcut(k, " toggling standby mode."); rs.ShowDialog(); if (rs.Shortcut != null) { settings.StandbyShortcut = rs.Shortcut; } } private void Hyperlink_ResetStandbyShortcut(object sender, RoutedEventArgs e) { settings.StandbyShortcut = settings.StandbyShortcutDefault; } private void Hyperlink_TriggerStandbyShortcut(object sender, RoutedEventArgs e) { settings.Standby = !settings.Standby; } #endregion #region Custom Icons private void S_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { switch(e.PropertyName) { case "ButtonIndicatorUseCustomIcons": if (settings.ButtonIndicatorUseCustomIcons) { textblockCustomIconInfoBuiltin.Visibility = Visibility.Collapsed; textblockCustomIconInfoCustom.Visibility = Visibility.Visible; } else { textblockCustomIconInfoBuiltin.Visibility = Visibility.Visible; textblockCustomIconInfoCustom.Visibility = Visibility.Collapsed; } break; } } private void OnButtonCustomIconsSelectFolder(object sender, RoutedEventArgs e) { using (System.Windows.Forms.FolderBrowserDialog dlg = new System.Windows.Forms.FolderBrowserDialog()) { dlg.Description = "Select folder with custom icons"; dlg.SelectedPath = settings.ButtonIndicatorCustomIconsFolder; dlg.ShowNewFolderButton = true; System.Windows.Forms.DialogResult result = dlg.ShowDialog(); if (result == System.Windows.Forms.DialogResult.OK) { settings.ButtonIndicatorCustomIconsFolder = dlg.SelectedPath; } } } private void OnButtonExportBuiltinIcons(object sender, RoutedEventArgs e) { using (System.Windows.Forms.FolderBrowserDialog dlg = new System.Windows.Forms.FolderBrowserDialog()) { dlg.Description = "Select folder for export"; // dlg.SelectedPath = settings.ButtonIndicatorCustomIconsFolder; dlg.ShowNewFolderButton = true; System.Windows.Forms.DialogResult result = dlg.ShowDialog(); if (result == System.Windows.Forms.DialogResult.OK) { ImageResources.ExportBuiltinRessources(dlg.SelectedPath); } } } private void OnClickCustomIconsHelp(object sender, RoutedEventArgs e) { } private void OnClickCustomIconsRefresh(object sender, RoutedEventArgs e) { if (settings.ButtonIndicatorUseCustomIcons) { ImageResources.ReloadRessources(settings.ButtonIndicatorCustomIconsFolder); } else { ImageResources.ReloadRessources(null); } } #endregion #region Other Hyperlinks private void Hyperlink_WelcomeWindow(object sender, RoutedEventArgs e) { ((App)Application.Current).showWelcomeWindow(); } private void Hyperlink_DisableStandbyMode(object sender, RoutedEventArgs e) { settings.Standby = false; } #endregion } } ================================================ FILE: KeyNStroke/SettingsStore.cs ================================================ using Microsoft.Win32; using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.IO; using System.IO.IsolatedStorage; using System.Linq; using System.Reflection; using System.Runtime.Serialization; using System.Runtime.Serialization.Json; using System.Text; using System.Threading.Tasks; using WpfColorFontDialog; namespace KeyNStroke { #region Enums for some settings public enum TextAlignment { Left, Right, Center } public enum TextDirection { Up, Down } public enum Style { NoAnimation, Slide } public enum ButtonIndicatorType { Disabled, PicsAroundCursor } public enum KeystrokeMethodEnum { [Description("Text mode")] TextMode = 1, [Description("Text mode (Backspace can delete text)")] TextModeBackspaceCanDeleteText = 2, [Description("Shortcut mode (no letters and no shift)")] ShortcutModeNoText = 3, [Description("Shortcut mode (with letters and shift)")] ShortcutModeWithText = 4 } public static class Extensions { public static bool IsTextMode(this KeystrokeMethodEnum method) { return method == KeystrokeMethodEnum.TextMode || method == KeystrokeMethodEnum.TextModeBackspaceCanDeleteText; } } #endregion #region Serializable classes for complex types interface IGet { T Get(); } [DataContract] public class SerializableFont : IGet { [DataMember] public string family; [DataMember] public SerializableColor color; [DataMember] public double size; [DataMember] public string style; [DataMember] public int stretch; [DataMember] public int weight; public SerializableFont(FontInfo value) { color = new SerializableColor(value.Color.Brush.Color); family = value.Family.Source; size = value.Size; stretch = value.Stretch.ToOpenTypeStretch(); if (value.Style == System.Windows.FontStyles.Italic) style = "Italic"; else if (value.Style == System.Windows.FontStyles.Oblique) style = "Oblique"; else style = "Normal"; weight = value.Weight.ToOpenTypeWeight(); } public FontInfo Get() { System.Windows.FontStyle style = System.Windows.FontStyles.Normal; if (this.style == "Italic") style = System.Windows.FontStyles.Italic; if (this.style == "Oblique") style = System.Windows.FontStyles.Oblique; return new FontInfo(new System.Windows.Media.FontFamily(family), size, style, System.Windows.FontStretch.FromOpenTypeStretch(stretch), System.Windows.FontWeight.FromOpenTypeWeight(weight), new System.Windows.Media.SolidColorBrush(color.Get())); } public override string ToString() { return $"{family}, {size}, {style}, stretch={stretch}, weight={weight}, #{color.r:X2}{color.g:X2}{color.b:X2}"; } } [DataContract] public class SerializablePoint : IGet { [DataMember] public double x; [DataMember] public double y; public SerializablePoint(System.Windows.Point value) { x = value.X; y = value.Y; } public System.Windows.Point Get() { return new System.Windows.Point(x, y); } } [DataContract] public class SerializableSize : IGet { [DataMember] public int width; [DataMember] public int height; public SerializableSize(Size value) { width = value.Width; height = value.Height; } public Size Get() { return new Size(width, height); } } [DataContract] public class SerializableColor : IGet { [DataMember] public byte a; [DataMember] public byte r; [DataMember] public byte g; [DataMember] public byte b; public SerializableColor(System.Windows.Media.Color value) { a = value.A; r = value.R; g = value.G; b = value.B; } public System.Windows.Media.Color Get() { return System.Windows.Media.Color.FromArgb(a, r, g, b); } } [DataContract] public class SerializableColor2 : IGet { [DataMember] public int color; public SerializableColor2(Color value) { color = value.ToArgb(); } public Color Get() { return Color.FromArgb(color); } } #endregion #region Serializable Settings [DataContract] class Settings { [DataMember] public SerializableFont labelFont = null; [DataMember] public SerializableColor2 labelColor = null; [DataMember] public SerializableColor2 backgroundColor = null; [DataMember] public Nullable labelTextAlignment = null; [DataMember] public Nullable labelTextDirection = null; [DataMember] public Nullable ================================================ FILE: KeyNStroke/TweenLabel.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Drawing.Imaging; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using KeyNStroke; namespace KeyNStroke { class TweenLabel : Control { #region Recycling static Stack unusedLabels = new Stack(15); static int debug_nextId = 1; int debug_id; void PrintDebug(String msg) { Log.e("TL", debug_id.ToString() + " y:" + this.Location.Y + " '" + this.Text + "' " + msg); } public static TweenLabel getNewLabel(Form form, SettingsStore s) { if (unusedLabels.Count > 0) { return unusedLabels.Pop().Init(form, s); } TweenLabel t = new TweenLabel().Init(form, s); t.debug_id = debug_nextId; debug_nextId += 1; return t; } public void Recycle() { PrintDebug("recyle"); this.Parent.Controls.Remove(this); this.Parent = null; this.Visible = false; attachedToTimer = false; if (TweenLabel.moveTimer != null) TweenLabel.moveTimer.Tick -= T_Move; this.historyTimeout = null; TweenLabel.unusedLabels.Push(this); } #endregion SettingsStore settings; private TweenLabel Init(Form form, SettingsStore s) { PrintDebug("revive"); if (settings == null) { settings = s; settings.PropertyChanged += settingChanged; this.TextChanged += TweenLabel_TextChanged; //this.Font = new Font(settings.LabelFont.; //this.ForeColor = settings.TextColor; this.DoubleBuffered = true; } return this; } void TweenLabel_TextChanged(object sender, EventArgs e) { ResetHistoryTimeoutTimer(); } private void settingChanged(object sender, PropertyChangedEventArgs e) { switch (e.PropertyName) { case "LabelFont": //this.Font = settings.LabelFont; break; case "TextColor": //this.ForeColor = settings.TextColor; break; case "HistoryTimeout": ResetHistoryTimeoutTimer(); break; } } public void tweenFadeIn() { PrintDebug("fade in start"); this.Opacity = 0; this.Visible = true; Timer T = new Timer(); T.Tick += T_FadeIn; T.Interval = 40; // bit more than 30 Hz T.Start(); } void T_FadeIn(object sender, EventArgs e) { this.Opacity += 0.12f; if (Opacity >= 1) { Timer T = (Timer)sender; T.Stop(); Opacity = 1; PrintDebug("fade in finished"); ResetHistoryTimeoutTimer(); } this.Refresh(); } Timer fadeOutTimer; Func fadeOutOnFinish; public void FadeOutAndRecycle(Func onFinish) { CancelTimeoutTimer(); if (fadeOutTimer == null) { fadeOutTimer = new Timer(); fadeOutTimer.Tick += T_FadeOut; } if (!fadeOutTimer.Enabled) { this.Opacity = 1; fadeOutOnFinish = onFinish; fadeOutTimer.Interval = 40; // bit more than 30 Hz PrintDebug("fade out start"); fadeOutTimer.Start(); CancelTimeoutTimer(); } } void T_FadeOut(object sender, EventArgs e) { this.Opacity -= 0.04f; if (Opacity <= 0) { fadeOutTimer.Stop(); Opacity = 0; if (fadeOutOnFinish != null) { fadeOutOnFinish(this); fadeOutOnFinish = null; } PrintDebug("fade out finish"); this.Recycle(); return; } this.Refresh(); } bool IsFadingOut() { return fadeOutTimer != null && fadeOutTimer.Enabled; } Point moveStartLocation; Point moveDir; float movePercent; bool attachedToTimer = false; static Timer moveTimer; // one timer for all for synchronous static int moveTimerUserCount = 0; public void TweenMove(Point newMoveDir) { if (moveTimer == null) { TweenLabel.moveTimer = new Timer(); TweenLabel.moveTimer.Interval = 40; // bit more than 30 Hz TweenLabel.moveTimer.Start(); } if (!attachedToTimer) { TweenLabel.moveTimer.Tick += T_Move; TweenLabel.moveTimerUserCount++; Log.e("TLMove", "Handler attached"); movePercent = 0; moveStartLocation = this.Location; moveDir = newMoveDir; attachedToTimer = true; } else { movePercent = (float)moveDir.Y * movePercent / ((float)newMoveDir.Y + moveDir.Y); moveDir = Point.Add(moveDir, new Size(newMoveDir)); } } void T_Move(object sender, EventArgs e) { movePercent += 0.2f; if (movePercent >= 1) { Timer T = (Timer)sender; T.Tick -= T_Move; Log.e("TLMove", "Handler removed"); attachedToTimer = false; moveTimerUserCount -= 1; this.Location = Point.Add(moveStartLocation, new Size(moveDir)); return; } this.Location = Point.Add(moveStartLocation, new Size((int)(moveDir.X * movePercent), (int)(moveDir.Y * movePercent))); } float opacity = 1f; public float Opacity { get { return opacity; } set { opacity = value; } } public void DrawString(System.Drawing.Graphics G) { Color C = Color.FromArgb(Math.Min((int)(opacity * 255f), 255), this.ForeColor); SolidBrush drawBrush = new SolidBrush(C); StringFormat drawFormat = new StringFormat(); SizeF StringSize = G.MeasureString(this.Text, this.Font); int x = 0; int y = 0; switch (settings.LabelTextAlignment) { case TextAlignment.Left: x = 0; break; case TextAlignment.Right: x = this.Width - (int)StringSize.Width; break; case TextAlignment.Center: x = (int)(((float)this.Width - StringSize.Width) / 2.0f); break; } // vertical center y = (int)(((float)this.Height - StringSize.Height) / 2.0f); using (Bitmap buffer = new Bitmap(this.Width, this.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb)) { using (Graphics graphics = Graphics.FromImage(buffer)) { TextRenderer.DrawText(graphics, this.Text, this.Font, new Point(x, y), C, Color.Transparent, TextFormatFlags.NoPadding); } ColorMatrix matrix = new ColorMatrix(); matrix.Matrix33 = opacity; ImageAttributes attributes = new ImageAttributes(); attributes.SetColorMatrix(matrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap); //now draw the image G.DrawImage(buffer, new Rectangle(0, 0, buffer.Width, buffer.Height), 0, 0, buffer.Width, buffer.Height, GraphicsUnit.Pixel, attributes); //G.DrawImageUnscaled(buffer, this.ClientRectangle); } //TextRenderer.DrawText(G, this.Text, this.Font, new Point(x, y), // C, Color.Transparent, TextFormatFlags.NoPadding); //G.DrawString(this.Text, this.Font, drawBrush, x, y, drawFormat); drawBrush.Dispose(); // Declare and instantiate a new pen. //System.Drawing.Pen myPen = new System.Drawing.Pen(Color.Aqua); // Draw an aqua rectangle in the rectangle represented by the control. //G.DrawRectangle(myPen, new Rectangle(new Point(0, 0), // Size.Subtract(this.Size, new Size(1, 1)))); } public bool AddingWouldFit(string additionalChars) { //SizeF StringSize = this.CreateGraphics().MeasureString( // this.Text + additionalChars, this.Font); Size StringSize2 = TextRenderer.MeasureText(this.CreateGraphics(), this.Text, this.Font, this.Size, TextFormatFlags.NoPadding); //return StringSize.Width < (this.Width - 2); return StringSize2.Width < (this.Width - 25); } protected override void OnPaint(PaintEventArgs e) { //base.OnPaint(e); DrawString(e.Graphics); } Timer timeoutTimer; public delegate void HistoryTimeOutEvent(TweenLabel l); public event HistoryTimeOutEvent historyTimeout; private void ResetHistoryTimeoutTimer() { PrintDebug("reset history timeout"); CancelTimeoutTimer(); if (!settings.EnableHistoryTimeout || IsFadingOut()) return; if (timeoutTimer == null) { timeoutTimer = new Timer(); timeoutTimer.Tick += timeoutTimer_Tick; } timeoutTimer.Interval = (int) (settings.HistoryTimeout * 1000); timeoutTimer.Start(); } private void CancelTimeoutTimer() { if (timeoutTimer != null) { timeoutTimer.Stop(); } } void timeoutTimer_Tick(object sender, EventArgs e) { PrintDebug("fire history timeout"); CancelTimeoutTimer(); if (historyTimeout != null) { historyTimeout.Invoke(this); } FadeOutAndRecycle(null); } } } ================================================ FILE: KeyNStroke/UIHelper.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Media; using MColor = System.Windows.Media.Color; using DColor = System.Drawing.Color; namespace KeyNStroke { class UIHelper { #region Helper to convert Drawing.Color to Windows.Media.Color public static MColor ToMediaColor(DColor color) { return MColor.FromArgb(color.A, color.R, color.G, color.B); } public static DColor ToDrawingColor(MColor color) { return DColor.FromArgb(color.A, color.R, color.G, color.B); } #endregion /// /// Finds a Child of a given item in the visual tree. /// /// A direct parent of the queried item. /// The type of the queried item. /// x:Name or Name of child. /// The first parent item that matches the submitted type parameter. /// If not matching item can be found, /// a null parent is being returned. public static T FindChild(DependencyObject parent, string childName) where T : DependencyObject { // Confirm parent and childName are valid. if (parent == null) return null; if (parent is FrameworkElement) ((FrameworkElement)parent).ApplyTemplate(); T foundChild = null; int childrenCount = VisualTreeHelper.GetChildrenCount(parent); for (int i = 0; i < childrenCount; i++) { var child = VisualTreeHelper.GetChild(parent, i); // If the child is not of the request child type child T childType = child as T; if (childType == null) { // recursively drill down the tree foundChild = FindChild(child, childName); // If the child is found, break so we do not overwrite the found child. if (foundChild != null) break; } else if (!string.IsNullOrEmpty(childName)) { var frameworkElement = child as FrameworkElement; // If the child's name is set for search if (frameworkElement != null && frameworkElement.Name == childName) { // if the child's name is of the request name foundChild = (T)child; break; } } else { // child element found. foundChild = (T)child; break; } } return foundChild; } } } ================================================ FILE: KeyNStroke/Updater/Admininstration.cs ================================================ using System; using System.IO; using System.Reflection; using System.Security.Cryptography; using System.Xml; namespace KeyNStroke.Updater { class Admininstration { // Files for key creation and testing (all in current working directory) const string PATH_PUB_KEY = "updateKey.pub.xml"; const string PATH_PRIV_KEY = "updateKey.priv.xml"; const string PATH_UPDATE_MANIFEST = "updateManifest.xml"; /// /// Checks the command line and executes administrative functions if the parameter is given. /// Returns true if the program should exit. /// /// static public bool HandleArgs(string[] args) { if (args.Length > 0) { if (args[0] == "--create-signing-keypair") { CreateSigningKeyPair(); return true; } if (args[0] == "--create-update-manifest") { CreateUpdateManifest(); return true; } if (args[0] == "--sign-update-manifest") { SignUpdateManifest(); return true; } if (args[0] == "--test-update") { DownloadAndVerifyManifestAndUpdate(); return true; } if (args[0] == "--test-signing") { TestSigning(); return true; } } return false; } /// /// Creates a new RSA key and saves it next to the executable. /// Put the public key into the Resources folder and bake it into the executable. /// Store the private key somewhere and never give it away. /// static void CreateSigningKeyPair() { RSA rsaKey = RSA.Create(); string pub = rsaKey.ToXmlString(false); string priv = rsaKey.ToXmlString(true); using (StreamWriter outputFile = new StreamWriter(PATH_PUB_KEY)) { outputFile.Write(pub); } using (StreamWriter outputFile = new StreamWriter(PATH_PRIV_KEY)) { outputFile.Write(priv); } } /// /// Load the private key from the current directory. /// /// The private key. static RSA GetPrivateKey() { RSA priv = RSA.Create(); using (var sr = new StreamReader(PATH_PRIV_KEY)) { priv.FromXmlString(sr.ReadToEnd()); } return priv; } /// /// Verifies that the embedded public key can be used to verify documents /// signed with the private key found in the working directory. /// The test result is written to the Colsole. /// static void TestSigning() { String dirOfExecutable = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location); String tmpDocumentPath = Path.Combine(dirOfExecutable, "test.xml"); // Sign a test document with the private key and serialize the document to the disk { RSA priv = GetPrivateKey(); string xmlmessage = @" 19834209 02/02/2002 "; XmlDocument xmlDoc = new XmlDocument { PreserveWhitespace = true }; xmlDoc.LoadXml(xmlmessage); Utils.SignXml(xmlDoc, priv); // xmlDoc.GetElementsByTagName("number")[0].InnerText = "123123"; // xmlDoc.GetElementsByTagName("root")[0].AppendChild(xmlDoc.CreateNode("element", "test", "")); xmlDoc.Save(tmpDocumentPath); } // Load the test document from the disk and verify the signature with the embedded public key { XmlDocument xmlDoc = new XmlDocument { PreserveWhitespace = true }; xmlDoc.Load(tmpDocumentPath); try { Utils.VerifyXml(xmlDoc, Utils.GetEmbeddedPubKey()); Console.WriteLine("The XML signature is valid."); Console.WriteLine(xmlDoc.OuterXml); } catch (Exception e) { Console.WriteLine("The XML signature is not valid."); Console.WriteLine(e.Message); Console.WriteLine(xmlDoc.OuterXml); } } } /// /// Creates a manifest that can be used to update to the current executed assembly. /// The manifest is saved to the current working directory. /// The manifest is not signed so the user can set the info etc. /// static void CreateUpdateManifest() { string executable = System.Reflection.Assembly.GetEntryAssembly().Location; string version = Assembly.GetExecutingAssembly().GetName().Version.ToString(3); string hash = Utils.SHA256OfFile(executable); string manifest = $@" {version} {DateTime.UtcNow:s} New release with bug fixes and new features https://github.com/Phaiax/Key-n-Stroke/raw/master/Releases/{version}/{Path.GetFileName(executable)} {hash} "; XmlDocument xmlDoc = new XmlDocument { PreserveWhitespace = true }; xmlDoc.LoadXml(manifest); xmlDoc.Save(PATH_UPDATE_MANIFEST); } /// /// Signs the manifest in the current working directory. /// static void SignUpdateManifest() { XmlDocument xmlDoc = new XmlDocument { PreserveWhitespace = true }; xmlDoc.Load(PATH_UPDATE_MANIFEST); Utils.SignXml(xmlDoc, GetPrivateKey()); xmlDoc.Save(PATH_UPDATE_MANIFEST); Console.WriteLine($"scp {PATH_UPDATE_MANIFEST} $SSHSERVER:html/key-n-stroke/"); string executable = System.Reflection.Assembly.GetEntryAssembly().Location; Console.WriteLine($"scp /{executable.Replace("\\", "/").Replace(":", "")} $SSHSERVER:html/key-n-stroke/"); } /// /// Download and verify the manifest and the executable. /// static void DownloadAndVerifyManifestAndUpdate() { try { Console.WriteLine("Downloading manifest ..."); XmlDocument manifest = Utils.DownloadAndVerifyManifest(); Console.WriteLine("Downloading update ..."); byte[] data = Utils.DownloadExecutableAndVerifyHash(manifest, (e) => { Console.Write($"\r {e}%"); }, new TimeSpan(0, 0, 10)); Console.WriteLine(); Console.WriteLine($"Manifest and update ({data.Length} bytes) are ok."); } catch (Exception e) { Console.WriteLine("ERROR:"); Console.WriteLine(e.ToString()); } } } } ================================================ FILE: KeyNStroke/Updater/Statemachine.cs ================================================ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; using System.Windows; using System.Xml; namespace KeyNStroke.Updater { /// /// This implements a singleton statemachine that handles the update process. /// /// There is an API to connect this statemachine to one or more UIs. /// The UIs are synchronized, so any UI is allowed to trigger actions, /// and results will be displayed in all connected UIs at once. /// /// The UI needs to provide the following widgets: /// - A button with variable text that can be disabled. /// - A oneline TextBlock for error/progress information. /// The UI needs to implement the following functionality: /// - Call Updater.Instance.UiButton_Click() on Button click. /// - Subscribe to the event Updater.Instance.onUiUpdate. /// - On every event, update the UI elements according to the parameter UiUpdateEventArgs of this event. /// - Call Updater.Instance.UiRefresh() once after setting up the event handler. /// - Handle the fact that the callback calls from a different thread. /// - Subscribe to the event Updater.Instance.onShutdownRequest and shutdown the application if this event occurs. /// public class Statemachine { #region Public API /// /// The UI state. /// One button and one oneline TextBlock must be available on each GUI that wants to provide a GUI for update functionality. /// (We abstract the UI state into this struct so we can embed the update functionality into multiple windows.) /// public class UiUpdateEventArgs { public string buttonText; public bool buttonEnabled; public string info; /// /// If this has more than 0 items the info shall be displayed as a hyperlink. On clicking the hyperlink, the details /// shall be displayed in a message window. /// public List details; } public delegate void UiUpdateEventHandler(object sender, UiUpdateEventArgs e); public delegate void ShutdownRequestEventHandler(object sender); /// /// Event that is triggered whenever the UI should change or if UiRefresh is called. /// public event UiUpdateEventHandler OnUiUpdate; /// /// Event that is triggered when an update is impending and the application must exit immediately. /// public event ShutdownRequestEventHandler OnShutdownRequest; /// /// A function that should be called if the UI button is clicked. /// /// Can be null. /// Can be null. public void UiButton_Click(object sender, RoutedEventArgs e) { switch (updateState) { case UpdateState.ManifestDownloadTriggerable: case UpdateState.NoUpdateAvailable: case UpdateState.ManifestDownloadFailed: case UpdateState.ManifestVerificationFailed: case UpdateState.UpdateVerificationFailed: case UpdateState.UpdateDownloadFailed: updateCommands.Add(UpdateCommands.DownloadManifest); break; case UpdateState.UpdateAvailable: updateCommands.Add(UpdateCommands.DownloadUpdate); break; case UpdateState.ManifestDownloading: case UpdateState.UpdateDownloading: case UpdateState.UpdateApplicationImpending: break; } } const String UI_CHECK_FOR_UPDATES = "Check for updates"; const String UI_NO_UPDATE_AVAILABLE = "No update available"; const String UI_CHECKING_FOR_UPDATES = "Checking for updates..."; const String UI_MANIFEST_DOWNLOAD_FAILED = "Manifest download failed."; const String UI_MANIFEST_VERIFICATION_FAILED = "Manifest verification failed."; const String UI_DO_UPDATE_TO_VERSION = "Update to version"; const String UI_WHATS_NEW_IN_VERSION = "What's new in version"; const String UI_UPDATE_DOWNLOAD_IN_PROGRESS = "Dowloading..."; const String UI_UPDATE_DOWNLOAD_FAILED = "Update download failed."; const String UI_UPDATE_VERIFICATION_FAILED = "Update verification failed."; const String UI_UPDATE_IMPENDING = "Installing... The application will restart in a few moments."; /// /// Trigger the UiUpdateEventHandler event. This should be called after a new window connected its UI to the updater. /// public void UiRefresh() { UiUpdateEventArgs eargs = new UiUpdateEventArgs { info = "", details = new List(0) }; switch (updateState) { case UpdateState.ManifestDownloadTriggerable: eargs.buttonText = UI_CHECK_FOR_UPDATES; eargs.buttonEnabled = true; break; case UpdateState.ManifestDownloading: eargs.buttonText = UI_CHECK_FOR_UPDATES; eargs.buttonEnabled = false; eargs.info = UI_CHECKING_FOR_UPDATES; break; case UpdateState.ManifestDownloadFailed: eargs.buttonText = UI_CHECK_FOR_UPDATES; eargs.buttonEnabled = true; eargs.info = UI_MANIFEST_DOWNLOAD_FAILED; eargs.details.Add(lastErrorMessage); break; case UpdateState.ManifestVerificationFailed: eargs.buttonText = UI_CHECK_FOR_UPDATES; eargs.buttonEnabled = true; eargs.info = UI_MANIFEST_VERIFICATION_FAILED; eargs.details.Add(lastErrorMessage); break; case UpdateState.NoUpdateAvailable: eargs.buttonText = UI_CHECK_FOR_UPDATES; eargs.buttonEnabled = true; eargs.info = UI_NO_UPDATE_AVAILABLE; break; case UpdateState.UpdateAvailable: eargs.buttonText = UI_DO_UPDATE_TO_VERSION + " " + nextVersion; eargs.buttonEnabled = true; eargs.info = UI_WHATS_NEW_IN_VERSION + " " + nextVersion; eargs.details.Add(nextVersionInfo); break; case UpdateState.UpdateDownloading: eargs.buttonText = UI_DO_UPDATE_TO_VERSION + " " + nextVersion; eargs.buttonEnabled = false; eargs.info = UI_UPDATE_DOWNLOAD_IN_PROGRESS + " (" + downloadPercentage.ToString() + "%)"; break; case UpdateState.UpdateVerificationFailed: eargs.buttonText = UI_CHECK_FOR_UPDATES; eargs.buttonEnabled = true; eargs.info = UI_UPDATE_VERIFICATION_FAILED; eargs.details.Add(lastErrorMessage); break; case UpdateState.UpdateDownloadFailed: eargs.buttonText = UI_CHECK_FOR_UPDATES; eargs.buttonEnabled = true; eargs.info = UI_UPDATE_DOWNLOAD_FAILED; eargs.details.Add(lastErrorMessage); break; case UpdateState.UpdateApplicationImpending: eargs.buttonText = UI_CHECK_FOR_UPDATES; eargs.buttonEnabled = false; eargs.info = UI_UPDATE_IMPENDING; break; default: break; } OnUiUpdate?.Invoke(this, eargs); } #endregion #region Singleton private static readonly Statemachine instance = new Statemachine(); /// /// Singleton /// public static Statemachine Instance { get { return instance; } } #endregion #region Constructor, Thread start private readonly Thread updateThread; private Statemachine() { updateThread = new Thread(new ThreadStart(UpdateThread)); updateThread.IsBackground = true; updateThread.Start(); } #endregion #region Variables /// /// A queue for communication from UI to the Update Thread /// readonly BlockingCollection updateCommands = new BlockingCollection(new ConcurrentQueue()); #region Infos for User/UI /// /// The latest error message. /// string lastErrorMessage = ""; /// /// The download progress during state UpdateDownloading /// int downloadPercentage = 0; /// /// The version number of the available update. /// string nextVersion = ""; /// /// The changelog for the available update. /// string nextVersionInfo = ""; #endregion #endregion private UpdateState updateState; public enum UpdateState { ManifestDownloadTriggerable, ManifestDownloading, ManifestDownloadFailed, ManifestVerificationFailed, // like ManifestDownloadTriggerable, but with error message NoUpdateAvailable, UpdateAvailable, UpdateDownloading, UpdateVerificationFailed, // like ManifestDownloadTriggerable, but with error message UpdateDownloadFailed, UpdateApplicationImpending, } private enum UpdateCommands { DownloadManifest, DownloadUpdate } void UpdateThread() { XmlDocument verifiedManifest = null; byte[] update_exe = null; while (true) { try { // Blocks until at least one new Item is available UpdateCommands command = updateCommands.Take(); // Only execute the last command in the queue, discard all others while (updateCommands.Count > 0) { command = updateCommands.Take(); } switch (updateState) { case UpdateState.ManifestDownloadTriggerable: case UpdateState.ManifestDownloadFailed: case UpdateState.ManifestVerificationFailed: case UpdateState.UpdateVerificationFailed: case UpdateState.UpdateDownloadFailed: case UpdateState.NoUpdateAvailable: if (command == UpdateCommands.DownloadManifest) { updateState = UpdateState.ManifestDownloading; UiRefresh(); try { XmlDocument m = Utils.DownloadAndVerifyManifest(); if (Utils.CanUpdate(m)) { updateState = UpdateState.UpdateAvailable; nextVersion = m.SelectSingleNode("manifest/update/version").InnerText; nextVersionInfo = m.SelectSingleNode("manifest/update/info").InnerText; verifiedManifest = m; } else { updateState = UpdateState.NoUpdateAvailable; } } catch (Utils.VerificationException e) { updateState = UpdateState.ManifestVerificationFailed; lastErrorMessage = "Verification failed: \n" + e.Message; } catch (Utils.DownloadFailedException e) { updateState = UpdateState.ManifestDownloadFailed; lastErrorMessage = "Download failed: \n" + e.Message; } catch (Exception e) { updateState = UpdateState.ManifestDownloadFailed; lastErrorMessage = "Unexpected error: \n" + e.Message; } } UiRefresh(); break; case UpdateState.ManifestDownloading: case UpdateState.UpdateDownloading: case UpdateState.UpdateApplicationImpending: // Will never happen here break; case UpdateState.UpdateAvailable: if (command == UpdateCommands.DownloadUpdate) { updateState = UpdateState.UpdateDownloading; UiRefresh(); try { update_exe = Utils.DownloadExecutableAndVerifyHash(verifiedManifest, (progress) => { downloadPercentage = progress; UiRefresh(); }, new TimeSpan(0, 5, 0)); updateState = UpdateState.UpdateApplicationImpending; UiRefresh(); Updater.TriggerUpdateStep2(update_exe); OnShutdownRequest?.Invoke(this); } catch (Utils.VerificationException e) { updateState = UpdateState.UpdateVerificationFailed; lastErrorMessage = "Verification failed: \n" + e.Message; } catch (Utils.DownloadFailedException e) { updateState = UpdateState.UpdateDownloadFailed; lastErrorMessage = "Download failed: \n" + e.Message; } catch (Exception e) { updateState = UpdateState.UpdateDownloadFailed; lastErrorMessage = "Unexpected error: \n" + e.Message; } } UiRefresh(); break; } } catch (InvalidOperationException) { return; } } } } } ================================================ FILE: KeyNStroke/Updater/Updater.cs ================================================ using System; using System.IO; using System.Diagnostics; namespace KeyNStroke.Updater { class Updater { public static Statemachine Instance { get { return Statemachine.Instance; } } private Updater() { } /// /// Returns true if the program should exit /// /// static public bool HandleArgs(string[] args) { if (args.Length > 0) { if (args[0] == "--update-step2" && args.Length == 3) { UpdateStep2(args[1], int.Parse(args[2])); return true; } if (args[0] == "--update-step3" && args.Length == 3) { UpdateStep3(args[1], int.Parse(args[2])); return false; // continue running } if (Admininstration.HandleArgs(args)) { return true; } } return false; } static public void TriggerUpdateStep2(byte[] update) { string oldExePath = System.Reflection.Assembly.GetEntryAssembly().Location; string tmpExeName = $"Key-n-Stroke_Updater.exe"; string oldExeParentFolder = Path.GetDirectoryName(oldExePath); // may not be writable string systemTmpFolder = Path.GetTempPath(); // Should be writable in any case string tmpExePath = Path.Combine(systemTmpFolder, tmpExeName); using (var fs = new FileStream(tmpExePath, FileMode.Create, FileAccess.Write)) { fs.Write(update, 0, update.Length); } int ownPid = Process.GetCurrentProcess().Id; ProcessStartInfo psi = new ProcessStartInfo { FileName = tmpExePath, WorkingDirectory = Path.GetDirectoryName(tmpExePath), Arguments = $"--update-step2 \"{oldExePath}\" {ownPid}" }; if (!Utils.IsDirectoryWritable(oldExeParentFolder)) { psi.UseShellExecute = true; psi.Verb = "runas"; } Process.Start(psi); // Shutdown own process immediately! } /// /// Handles the swap of the executables. /// The we are the updated executable and the previous executable (the one that started us) must be overwritten by us. /// /// static public void UpdateStep2(string oldExePath, int startingProcessPid) { try { Process orig = Process.GetProcessById(startingProcessPid); if (!orig.WaitForExit(60000)) { Console.WriteLine("ERROR: Calling process did not exit in time"); return; } orig.Close(); } catch (ArgumentException) { // The process specified by the processId parameter is not running. The identifier might be expired. } string tmpExePath = System.Reflection.Assembly.GetEntryAssembly().Location; Log.e("UPDATE", $"Copy from {tmpExePath} to {oldExePath}"); if (File.Exists(oldExePath)) { File.Delete(oldExePath); } File.Copy(tmpExePath, oldExePath); int ownPid = Process.GetCurrentProcess().Id; ProcessStartInfo psi = new ProcessStartInfo { FileName = oldExePath, WorkingDirectory = Path.GetDirectoryName(oldExePath), Arguments = $"--update-step3 \"{tmpExePath}\" {ownPid}" }; Process.Start(psi); // Even if the updater has been started as Admin, this will start as the original user again. // Shutdown own process immediately } /// /// Delete the temporary executable /// /// /// static public void UpdateStep3(string tmpExePath, int startingProcessPid) { try { Process orig = Process.GetProcessById(startingProcessPid); if (!orig.WaitForExit(60000)) { Console.WriteLine("ERROR: Calling process did not exit in time"); return; } orig.Close(); } catch (ArgumentException) { // The process specified by the processId parameter is not running. The identifier might be expired. } if (File.Exists(tmpExePath)) { Log.e("UPDATE", $"Deleted {tmpExePath}"); File.Delete(tmpExePath); } } } } ================================================ FILE: KeyNStroke/Updater/Utils.cs ================================================ using System; using System.IO; using System.Net; using System.Reflection; using System.Security.Cryptography; using System.Security.Cryptography.Xml; using System.Security.Principal; using System.Text; using System.Threading; using System.Windows; using System.Xml; namespace KeyNStroke.Updater { class Utils { #region Exceptions /// /// Exception thrown if the verification of the manifest or update.exe failed. /// public class VerificationException : Exception { public VerificationException() { } public VerificationException(string message) : base(message) { } public VerificationException(string message, Exception inner) : base(message, inner) { } } /// /// Exception thrown if the download of the manifest or update.exe failed. /// public class DownloadFailedException : Exception { public DownloadFailedException() { } public DownloadFailedException(string message) : base(message) { } public DownloadFailedException(string message, Exception inner) : base(message, inner) { } } #endregion #region Hash /// /// Calculates the SHA256 hash of the given file. /// /// /// The hash as string. public static string SHA256OfFile(string path) { SHA256 Sha256 = SHA256.Create(); using (FileStream stream = File.OpenRead(path)) { byte[] bytes = Sha256.ComputeHash(stream); string result = ""; foreach (byte b in bytes) result += b.ToString("x2"); return result; } } /// /// Calculates the SHA256 hash of the given bytes. /// /// /// The hash as string. public static string SHA256OfBytes(byte[] data) { SHA256 Sha256 = SHA256.Create(); byte[] bytes = Sha256.ComputeHash(data); string result = ""; foreach (byte b in bytes) result += b.ToString("x2"); return result; } #endregion #region Permissions public static bool IsDirectoryWritable(string path) { try { string random = Path.Combine(path, Path.GetRandomFileName()); using (FileStream fs = File.Create(random, 1, FileOptions.DeleteOnClose)) { } return true; } catch { return false; } } public static bool HasAdminPrivileges() { using (WindowsIdentity identity = WindowsIdentity.GetCurrent()) { WindowsPrincipal principal = new WindowsPrincipal(identity); return principal.IsInRole(WindowsBuiltInRole.Administrator); } } #endregion #region XML signature // https://docs.microsoft.com/de-de/dotnet/standard/security/how-to-sign-xml-documents-with-digital-signatures /// /// Sign an XML file with an RSA key. /// The signature can be verified with the other key of the key pair by using the function VerifyXml(). /// The singature is appended as an additional node to `xmlDoc`. /// /// The document to be signed /// The public or private key for signing. public static void SignXml(XmlDocument xmlDoc, RSA rsaKey) { // Check arguments. if (xmlDoc == null) throw new ArgumentNullException(nameof(xmlDoc)); // Create a SignedXml object. SignedXml signedXml = new SignedXml(xmlDoc) { // Add the key to the SignedXml document. SigningKey = rsaKey ?? throw new ArgumentNullException(nameof(rsaKey)) }; // Create a reference to be signed. Reference reference = new Reference { Uri = "" }; // Add an enveloped transformation to the reference. XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform(); reference.AddTransform(env); // Add the reference to the SignedXml object. signedXml.AddReference(reference); // Compute the signature. signedXml.ComputeSignature(); // Get the XML representation of the signature and save // it to an XmlElement object. XmlElement xmlDigitalSignature = signedXml.GetXml(); // Append the element to the XML document. xmlDoc.DocumentElement.AppendChild(xmlDoc.ImportNode(xmlDigitalSignature, true)); } // https://docs.microsoft.com/de-de/dotnet/standard/security/how-to-sign-xml-documents-with-digital-signatures /// /// Verify the signature of an XML document signed by the function SignXml(). /// Throws an exception if the signature is not valid. /// /// The signed document. /// The public or private key (other one) that has been used for signing. /// /// Wrong arguments /// Verification failed public static void VerifyXml(XmlDocument xmlDoc, RSA rsaKey) { // Check arguments. if (xmlDoc == null) throw new ArgumentNullException("xmlDoc"); if (rsaKey == null) throw new ArgumentNullException("pubKey"); // Create a new SignedXml object and pass it // the XML document class. SignedXml signedXml = new SignedXml(xmlDoc); // Find the "Signature" node and create a new // XmlNodeList object. XmlNodeList nodeList = xmlDoc.GetElementsByTagName("Signature"); // Throw an exception if no signature was found. if (nodeList.Count <= 0) { throw new CryptographicException("No Signature was found in the document."); } // This example only supports one signature for // the entire XML document. Throw an exception // if more than one signature was found. if (nodeList.Count > 1) { throw new CryptographicException("More that one signature was found in the document."); } // Load the first node. signedXml.LoadXml((XmlElement)nodeList[0]); // Check the signature and return the result. bool valid = signedXml.CheckSignature(rsaKey); if (!valid) { throw new CryptographicException("Signature invalid."); } } #endregion #region Download Helper public delegate void ProgressCallBack(int progress); /// Downloads a ressource from the web. Provides progress information /// The downloaded bytes (never null) /// Thrown if the download fails. private static byte[] DownloadWithTimeoutAndProgress(string url, ProgressCallBack progressCallBack, TimeSpan? timeout) { Exception exception = null; // probably a WebException byte[] data = null; bool canceled = false; using (WebClient wc = new WebClient()) { wc.DownloadProgressChanged += (o, e) => { bool _canceled; lock (e.UserState) { _canceled = canceled; } if (!canceled) { progressCallBack?.Invoke(e.ProgressPercentage); } }; wc.DownloadDataCompleted += (o, e) => { if (e.Error != null) { exception = e.Error; } else { data = e.Result; } lock (e.UserState) { //releases blocked thread Monitor.Pulse(e.UserState); } }; var syncObject = new Object(); lock (syncObject) { SynchronizationContext orig = SynchronizationContext.Current; // Use thread pool and not the SyncContext for WPF //SynchronizationContext.SetSynchronizationContext(new SynchronizationContext()); wc.DownloadDataAsync(new Uri(url), syncObject); SynchronizationContext.SetSynchronizationContext(orig); if (timeout is TimeSpan to) { if (!Monitor.Wait(syncObject, to)) { wc.CancelAsync(); throw new DownloadFailedException(exception.Message, exception); } } else { Monitor.Wait(syncObject); } } } if (exception != null) { throw new DownloadFailedException(exception.Message, exception); } if (data == null) { throw new DownloadFailedException("No data received."); } return data; } #endregion #region Download Update.exe /// /// Downloads the updated executable from the manifest and verifies its hash against the manifest. /// Throws an exception on download failure or verification failure. /// /// The verified manifest that specifies the hash and download url. /// Called on download progress change. This call originates from a different thread but only as long as this function blocks. /// The downloaded data. /// Thrown if the download fails. /// Thrown if the verification fails. public static byte[] DownloadExecutableAndVerifyHash(XmlDocument manifest, ProgressCallBack progressCallBack, TimeSpan? timeout) { var executableUrl = manifest.SelectSingleNode("manifest/update/download").InnerText; byte[] data = DownloadWithTimeoutAndProgress(executableUrl, progressCallBack, timeout); var expectedHash = manifest.SelectSingleNode("manifest/update/sha256").InnerText; var hash = SHA256OfBytes(data); if (hash != expectedHash) { throw new VerificationException("Update hash mismatch."); } else { return data; } } #endregion #region Download Manifest public static string ManifestUrl { get { return @"https://invisibletower.de/key-n-stroke/updateManifest.xml"; } } /// /// For debugging: Remember the latest downloaded manifest even if verification failed /// private static string _rawManifest; /// /// Download and verify the update manifest. /// Throws VerificationException if the manifest can't be validated. /// /// Returns the verified manifest. /// Thrown if the download failed. /// Thrown if the signature could not be verified. public static XmlDocument DownloadAndVerifyManifest() { XmlDocument xmlDoc = new XmlDocument { PreserveWhitespace = true }; TimeSpan oneMinute = new TimeSpan(0, 0, 3); byte[] xml = DownloadWithTimeoutAndProgress(ManifestUrl, null, oneMinute); try { _rawManifest = Encoding.UTF8.GetString(xml); // store for debug } catch (Exception e) { throw new VerificationException("Manifest invalid (no UTF-8)", e); } try { xmlDoc.LoadXml(_rawManifest); } catch (XmlException e) { throw new VerificationException("Manifest invalid (no xml)", e); } try { VerifyXml(xmlDoc, GetEmbeddedPubKey()); } catch (CryptographicException e) { throw new VerificationException(e.Message); } return xmlDoc; } #endregion #region GetEmbeddedPubKey /// /// Return the embedded PubKey /// /// public static RSA GetEmbeddedPubKey() { Stream pubKeyStream = Application.GetResourceStream(new Uri("pack://application:,,,/Key-n-Stroke;component/Resources/updateKey.pub.xml")).Stream; RSA pub = RSA.Create(); using (StreamReader sr = new StreamReader(pubKeyStream)) { pub.FromXmlString(sr.ReadToEnd()); } return pub; } #endregion #region Version compare (CanUpdate()) /// /// Checks if the updatable version is greater than the installed version. /// /// public static bool CanUpdate(XmlDocument manifest) { try { string update_version = manifest.SelectSingleNode("manifest/update/version").InnerText; Version current = Assembly.GetExecutingAssembly().GetName().Version; Version update = Version.Parse(update_version); return (update.CompareTo(current) > 0); } catch (Exception) { return false; } } #endregion } } ================================================ FILE: KeyNStroke/UrlOpener.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; namespace KeyNStroke { public class UrlOpener { public static void OpenGithub() { string url = "https://github.com/Phaiax/Key-n-Stroke/"; ProcessStartInfo si = new ProcessStartInfo(url); Process.Start(si); } public static void OpenGithubREADME() { string url = "https://github.com/Phaiax/Key-n-Stroke/blob/master/README.md"; ProcessStartInfo si = new ProcessStartInfo(url); Process.Start(si); } public static void OpenGithubIssues() { string url = "https://github.com/Phaiax/Key-n-Stroke/issues"; ProcessStartInfo si = new ProcessStartInfo(url); Process.Start(si); } } } ================================================ FILE: KeyNStroke/Welcome.xaml ================================================  settings.