From 86e5dcd3185a02e628c2bf9f5a9058a671713d23 Mon Sep 17 00:00:00 2001 From: Herwin Bozet Date: Sun, 19 Jan 2025 23:08:37 +0100 Subject: [PATCH] Initial commit Update .gitignore, ConsoleApp1.csproj, and 23 more files... --- .gitignore | 14 ++ ConsoleApp1/ConsoleApp1.csproj | 16 +++ ConsoleApp1/Program.cs | 15 +++ LICENSE | 121 ++++++++++++++++++ .../BasicExecutor.cs | 7 + NibblePoker.Flemmotron.Commons/IExecutor.cs | 12 ++ .../NibblePoker.Flemmotron.Commons.csproj | 9 ++ .../Utils/ProcessUtils.cs | 27 ++++ .../Utils/SendKeys.cs | 35 +++++ .../Utils/WindowUtils.cs | 22 ++++ NibblePoker.Flemmotron.TestLib/Class1.cs | 3 + .../NibblePoker.Flemmotron.TestLib.csproj | 9 ++ NibblePoker.Flemmotron.sln | 40 ++++++ NibblePoker.Win32Bindings/Kernel32.cs | 16 +++ .../NibblePoker.Win32Bindings.csproj | 13 ++ NibblePoker.Win32Bindings/User32.cs | 86 +++++++++++++ .../WinUser/SendKeys/EEventTypes.cs | 24 ++++ .../WinUser/SendKeys/EKeyEventFlags.cs | 40 ++++++ .../WinUser/SendKeys/EMouseEventFlags.cs | 114 +++++++++++++++++ .../WinUser/SendKeys/Imports.cs | 8 ++ .../WinUser/SendKeys/Structures.cs | 44 +++++++ NibblePoker.Win32Wrappers/Beeps.cs | 45 +++++++ NibblePoker.Win32Wrappers/MessageBox.cs | 52 ++++++++ .../NibblePoker.Win32Wrappers.csproj | 13 ++ icon.png | Bin 0 -> 7881 bytes 25 files changed, 785 insertions(+) create mode 100644 .gitignore create mode 100644 ConsoleApp1/ConsoleApp1.csproj create mode 100644 ConsoleApp1/Program.cs create mode 100644 LICENSE create mode 100644 NibblePoker.Flemmotron.Commons/BasicExecutor.cs create mode 100644 NibblePoker.Flemmotron.Commons/IExecutor.cs create mode 100644 NibblePoker.Flemmotron.Commons/NibblePoker.Flemmotron.Commons.csproj create mode 100644 NibblePoker.Flemmotron.Commons/Utils/ProcessUtils.cs create mode 100644 NibblePoker.Flemmotron.Commons/Utils/SendKeys.cs create mode 100644 NibblePoker.Flemmotron.Commons/Utils/WindowUtils.cs create mode 100644 NibblePoker.Flemmotron.TestLib/Class1.cs create mode 100644 NibblePoker.Flemmotron.TestLib/NibblePoker.Flemmotron.TestLib.csproj create mode 100644 NibblePoker.Flemmotron.sln create mode 100644 NibblePoker.Win32Bindings/Kernel32.cs create mode 100644 NibblePoker.Win32Bindings/NibblePoker.Win32Bindings.csproj create mode 100644 NibblePoker.Win32Bindings/User32.cs create mode 100644 NibblePoker.Win32Bindings/WinUser/SendKeys/EEventTypes.cs create mode 100644 NibblePoker.Win32Bindings/WinUser/SendKeys/EKeyEventFlags.cs create mode 100644 NibblePoker.Win32Bindings/WinUser/SendKeys/EMouseEventFlags.cs create mode 100644 NibblePoker.Win32Bindings/WinUser/SendKeys/Imports.cs create mode 100644 NibblePoker.Win32Bindings/WinUser/SendKeys/Structures.cs create mode 100644 NibblePoker.Win32Wrappers/Beeps.cs create mode 100644 NibblePoker.Win32Wrappers/MessageBox.cs create mode 100644 NibblePoker.Win32Wrappers/NibblePoker.Win32Wrappers.csproj create mode 100644 icon.png diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..319ca0a --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# IDEs +.vs/ +.idea/ + +# Build Artifacts +bin/ +obj/ +*.dll +*.exe + +# Trash +/packages/ +riderModule.iml +/_ReSharper.Caches/ diff --git a/ConsoleApp1/ConsoleApp1.csproj b/ConsoleApp1/ConsoleApp1.csproj new file mode 100644 index 0000000..4f6a2c9 --- /dev/null +++ b/ConsoleApp1/ConsoleApp1.csproj @@ -0,0 +1,16 @@ + + + + Exe + net8.0 + enable + enable + true + true + + + + + + + diff --git a/ConsoleApp1/Program.cs b/ConsoleApp1/Program.cs new file mode 100644 index 0000000..6b4c0cf --- /dev/null +++ b/ConsoleApp1/Program.cs @@ -0,0 +1,15 @@ +using NibblePoker.Win32Wrappers; + +namespace ConsoleApp1; + +//https://getandplay.github.io/2019/05/08/Find%20the%20window%20you%20wanted%20by%20using%20EnumWindows/ + +class Program { + public static int Main(string[] args) { + + + + Console.WriteLine("Hello, World!"); + return 0; + } +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0e259d4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/NibblePoker.Flemmotron.Commons/BasicExecutor.cs b/NibblePoker.Flemmotron.Commons/BasicExecutor.cs new file mode 100644 index 0000000..ee4031d --- /dev/null +++ b/NibblePoker.Flemmotron.Commons/BasicExecutor.cs @@ -0,0 +1,7 @@ +namespace NibblePoker.Flemmotron.Commons; + +public class BasicExecutor : IExecutor { + + + +} diff --git a/NibblePoker.Flemmotron.Commons/IExecutor.cs b/NibblePoker.Flemmotron.Commons/IExecutor.cs new file mode 100644 index 0000000..078b1fd --- /dev/null +++ b/NibblePoker.Flemmotron.Commons/IExecutor.cs @@ -0,0 +1,12 @@ +namespace NibblePoker.Flemmotron.Commons; + +public interface IExecutor { + + /*#region Properties + + public readonly List prerequisiteTasks; + + + #endregion*/ + +} diff --git a/NibblePoker.Flemmotron.Commons/NibblePoker.Flemmotron.Commons.csproj b/NibblePoker.Flemmotron.Commons/NibblePoker.Flemmotron.Commons.csproj new file mode 100644 index 0000000..3a63532 --- /dev/null +++ b/NibblePoker.Flemmotron.Commons/NibblePoker.Flemmotron.Commons.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/NibblePoker.Flemmotron.Commons/Utils/ProcessUtils.cs b/NibblePoker.Flemmotron.Commons/Utils/ProcessUtils.cs new file mode 100644 index 0000000..849d1f2 --- /dev/null +++ b/NibblePoker.Flemmotron.Commons/Utils/ProcessUtils.cs @@ -0,0 +1,27 @@ +namespace NibblePoker.Flemmotron.Commons.Utils; + +public class ProcessUtils { + public static IntPtr GetProcess() { + return IntPtr.Zero; + } + + /*[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool EnumWindows(EnumedWindow lpEnumFunc, ArrayList lParam); + + public static ArrayList GetWindows() + { + ArrayList windowHandles = new ArrayList(); + EnumedWindow callBackPtr = GetWindowHandle; + EnumWindows(callBackPtr, windowHandles); + + return windowHandles; + } + + private static bool GetWindowHandle(IntPtr windowHandle, ArrayList windowHandles) + { + windowHandles.Add(windowHandle); + return true; + }*/ + +} diff --git a/NibblePoker.Flemmotron.Commons/Utils/SendKeys.cs b/NibblePoker.Flemmotron.Commons/Utils/SendKeys.cs new file mode 100644 index 0000000..d4f9b47 --- /dev/null +++ b/NibblePoker.Flemmotron.Commons/Utils/SendKeys.cs @@ -0,0 +1,35 @@ +namespace NibblePoker.Flemmotron.Commons.Utils; + +public class SendKeys { + + /*public static async Task Main(string[] args) { + // Simulate a left mouse button click + var input = new INPUT[2]; + + // Mouse down + input[0] = new INPUT { + type = INPUT_MOUSE, + U = new InputUnion { + mi = new MOUSEINPUT { + dwFlags = MOUSEEVENTF_LEFTDOWN, + } + } + }; + + // Mouse up + input[1] = new INPUT { + type = INPUT_MOUSE, + U = new InputUnion { + mi = new MOUSEINPUT { + dwFlags = MOUSEEVENTF_LEFTUP, + } + } + }; + + // Send the input + Imports.SendInput((uint)input.Length, input, Marshal.SizeOf(typeof(INPUT))); + + // Optional delay + await Task.Delay(300); + }*/ +} diff --git a/NibblePoker.Flemmotron.Commons/Utils/WindowUtils.cs b/NibblePoker.Flemmotron.Commons/Utils/WindowUtils.cs new file mode 100644 index 0000000..d915822 --- /dev/null +++ b/NibblePoker.Flemmotron.Commons/Utils/WindowUtils.cs @@ -0,0 +1,22 @@ +namespace NibblePoker.Flemmotron.Commons.Utils; + +public class WindowUtils { + /*public static IReadOnlyList FindWindowByClassName(string className) + { + var windowList = new List(); + EnumWindows(OnWindowEnum, 0); + return windowList; + + bool OnWindowEnum(int hwnd, int lparam) + { + var lpString = new StringBuilder(512); + GetClassName(hwnd, lpString, lpString.Capacity); + if (lpString.ToString().Equals(className, StringComparison.InvariantCultureIgnoreCase)) + { + windowList.Add(hwnd); + } + + return true; + } + }*/ +} diff --git a/NibblePoker.Flemmotron.TestLib/Class1.cs b/NibblePoker.Flemmotron.TestLib/Class1.cs new file mode 100644 index 0000000..368a2cd --- /dev/null +++ b/NibblePoker.Flemmotron.TestLib/Class1.cs @@ -0,0 +1,3 @@ +namespace NibblePoker.Flemmotron.TestLib; + +public class Class1 { } diff --git a/NibblePoker.Flemmotron.TestLib/NibblePoker.Flemmotron.TestLib.csproj b/NibblePoker.Flemmotron.TestLib/NibblePoker.Flemmotron.TestLib.csproj new file mode 100644 index 0000000..3a63532 --- /dev/null +++ b/NibblePoker.Flemmotron.TestLib/NibblePoker.Flemmotron.TestLib.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/NibblePoker.Flemmotron.sln b/NibblePoker.Flemmotron.sln new file mode 100644 index 0000000..9cfe69c --- /dev/null +++ b/NibblePoker.Flemmotron.sln @@ -0,0 +1,40 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NibblePoker.Flemmotron.TestLib", "NibblePoker.Flemmotron.TestLib\NibblePoker.Flemmotron.TestLib.csproj", "{B72FCF67-410C-4AB9-93EB-138AA997025E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NibblePoker.Flemmotron.Commons", "NibblePoker.Flemmotron.Commons\NibblePoker.Flemmotron.Commons.csproj", "{9B454754-933F-49E3-9EA1-A161CDEB02D6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NibblePoker.Win32Bindings", "NibblePoker.Win32Bindings\NibblePoker.Win32Bindings.csproj", "{D59AA77B-F9F4-4EE7-857C-C0B95489655B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NibblePoker.Win32Wrappers", "NibblePoker.Win32Wrappers\NibblePoker.Win32Wrappers.csproj", "{FDA327CC-C600-4CCB-B3D9-2585B1C7C5E5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApp1", "ConsoleApp1\ConsoleApp1.csproj", "{EA368610-B371-40AF-98C6-3AFB46D7209E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B72FCF67-410C-4AB9-93EB-138AA997025E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B72FCF67-410C-4AB9-93EB-138AA997025E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B72FCF67-410C-4AB9-93EB-138AA997025E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B72FCF67-410C-4AB9-93EB-138AA997025E}.Release|Any CPU.Build.0 = Release|Any CPU + {9B454754-933F-49E3-9EA1-A161CDEB02D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9B454754-933F-49E3-9EA1-A161CDEB02D6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9B454754-933F-49E3-9EA1-A161CDEB02D6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9B454754-933F-49E3-9EA1-A161CDEB02D6}.Release|Any CPU.Build.0 = Release|Any CPU + {D59AA77B-F9F4-4EE7-857C-C0B95489655B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D59AA77B-F9F4-4EE7-857C-C0B95489655B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D59AA77B-F9F4-4EE7-857C-C0B95489655B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D59AA77B-F9F4-4EE7-857C-C0B95489655B}.Release|Any CPU.Build.0 = Release|Any CPU + {FDA327CC-C600-4CCB-B3D9-2585B1C7C5E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FDA327CC-C600-4CCB-B3D9-2585B1C7C5E5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FDA327CC-C600-4CCB-B3D9-2585B1C7C5E5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FDA327CC-C600-4CCB-B3D9-2585B1C7C5E5}.Release|Any CPU.Build.0 = Release|Any CPU + {EA368610-B371-40AF-98C6-3AFB46D7209E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EA368610-B371-40AF-98C6-3AFB46D7209E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EA368610-B371-40AF-98C6-3AFB46D7209E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EA368610-B371-40AF-98C6-3AFB46D7209E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/NibblePoker.Win32Bindings/Kernel32.cs b/NibblePoker.Win32Bindings/Kernel32.cs new file mode 100644 index 0000000..2649a0d --- /dev/null +++ b/NibblePoker.Win32Bindings/Kernel32.cs @@ -0,0 +1,16 @@ +using System.Runtime.InteropServices; + +namespace NibblePoker.Win32Bindings; + +// ReSharper disable UnusedMember.Global +public static class Kernel32 { + + /// https://learn.microsoft.com/en-us/windows/win32/api/utilapiset/nf-utilapiset-beep + /// + /// After some testing on a Windows 11 23H2 machine, it appears that the frequency limits are not enforced. + /// + [DllImport("kernel32.dll", CharSet = CharSet.None, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool Beep([In] uint dwFreq, [In] uint dwDuration); + +} diff --git a/NibblePoker.Win32Bindings/NibblePoker.Win32Bindings.csproj b/NibblePoker.Win32Bindings/NibblePoker.Win32Bindings.csproj new file mode 100644 index 0000000..a431a22 --- /dev/null +++ b/NibblePoker.Win32Bindings/NibblePoker.Win32Bindings.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/NibblePoker.Win32Bindings/User32.cs b/NibblePoker.Win32Bindings/User32.cs new file mode 100644 index 0000000..2b02332 --- /dev/null +++ b/NibblePoker.Win32Bindings/User32.cs @@ -0,0 +1,86 @@ +using System.Runtime.InteropServices; + +namespace NibblePoker.Win32Bindings; + +// ReSharper disable InconsistentNaming +// ReSharper disable UnusedMember.Global +public class User32 { + + #region MB_* flags + + public const uint MB_ABORTRETRYIGNORE = 0x00000002; + public const uint MB_CANCELTRYCONTINUE = 0x00000006; + public const uint MB_HELP = 0x00004000; + public const uint MB_OK = 0x00000000; + public const uint MB_OKCANCEL = 0x00000001; + public const uint MB_RETRYCANCEL = 0x00000005; + public const uint MB_YESNO = 0x00000004; + public const uint MB_YESNOCANCEL = 0x00000003; + + public const uint MB_ICONEXCLAMATION = 0x00000030; + public const uint MB_ICONWARNING = 0x00000030; + public const uint MB_ICONINFORMATION = 0x00000040; + public const uint MB_ICONASTERISK = 0x00000040; + public const uint MB_ICONQUESTION = 0x00000020; + public const uint MB_ICONSTOP = 0x00000010; + public const uint MB_ICONERROR = 0x00000010; + public const uint MB_ICONHAND = 0x00000010; + + public const uint MB_DEFBUTTON1 = 0x00000000; + public const uint MB_DEFBUTTON2 = 0x00000100; + public const uint MB_DEFBUTTON3 = 0x00000200; + public const uint MB_DEFBUTTON4 = 0x00000300; + + public const uint MB_APPLMODAL = 0x00000000; + public const uint MB_SYSTEMMODAL = 0x00001000; + public const uint MB_TASKMODAL = 0x00002000; + + public const uint MB_DEFAULT_DESKTOP_ONLY = 0x00020000; + public const uint MB_RIGHT = 0x00080000; + public const uint MB_RTLREADING = 0x00100000; + public const uint MB_SETFOREGROUND = 0x00010000; + public const uint MB_TOPMOST = 0x00040000; + public const uint MB_SERVICE_NOTIFICATION = 0x00200000; + + /// + /// Used by for the sound type that is propagated to internally. + /// + public const uint MB_FALLBACK = 0xFFFFFFFF; + + #endregion + + + #region MessageBox Returns + + public const int IDABORT = 3; + public const int IDCANCEL = 2; + public const int IDCONTINUE = 11; + public const int IDIGNORE = 5; + public const int IDNO = 7; + public const int IDOK = 1; + public const int IDRETRY = 4; + public const int IDTRYAGAIN = 10; + public const int IDYES = 6; + + #endregion + + /// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebeep + [DllImport("user32.dll", CharSet = CharSet.None, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool MessageBeep(uint uType); + + [DllImport("user32.dll", CharSet = CharSet.Ansi, SetLastError = true)] + [return: MarshalAs(UnmanagedType.I4)] + public static extern int MessageBoxA(IntPtr hWnd, nint lpText, nint lpCaption, uint uType); + + [DllImport("user32.dll", CharSet = CharSet.Ansi, SetLastError = true)] + [return: MarshalAs(UnmanagedType.I4)] + public static extern int MessageBoxW(IntPtr hWnd, nint lpText, nint lpCaption, uint uType); + + //[DllImport("user32.dll")] + //public static extern int GetClassName(int hWnd, StringBuilder lpString, int nMaxCount); + + //[DllImport("user32")] + //public static extern int GetWindowText(int hwnd, StringBuilder lptrString, int nMaxCount); + +} diff --git a/NibblePoker.Win32Bindings/WinUser/SendKeys/EEventTypes.cs b/NibblePoker.Win32Bindings/WinUser/SendKeys/EEventTypes.cs new file mode 100644 index 0000000..0683768 --- /dev/null +++ b/NibblePoker.Win32Bindings/WinUser/SendKeys/EEventTypes.cs @@ -0,0 +1,24 @@ +namespace NibblePoker.Win32Bindings.WinUser.SendKeys; + +public enum EEventTypes { + /// + /// The event is a mouse event. Use the mi structure of the union. + /// + /// + /// + INPUT_MOUSE = 0, + + /// + /// The event is a keyboard event. Use the ki structure of the union. + /// + /// + /// + INPUT_KEYBOARD = 1, + + /// + /// The event is a hardware event. Use the hi structure of the union. + /// + /// + /// + INPUT_HARDWARE = 2, +} diff --git a/NibblePoker.Win32Bindings/WinUser/SendKeys/EKeyEventFlags.cs b/NibblePoker.Win32Bindings/WinUser/SendKeys/EKeyEventFlags.cs new file mode 100644 index 0000000..f1eb2a5 --- /dev/null +++ b/NibblePoker.Win32Bindings/WinUser/SendKeys/EKeyEventFlags.cs @@ -0,0 +1,40 @@ +namespace NibblePoker.Win32Bindings.WinUser.SendKeys; + +// ReSharper disable InconsistentNaming +[Flags] +public enum EKeyEventFlags : uint { + // https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-keybdinput + + /// + /// If specified, the wScan scan code consists of a sequence of two bytes, where the first byte has a value of 0xE0. + /// See Extended-Key Flag + /// for more info. + /// + /// + /// + KEYEVENTF_EXTENDEDKEY = 0x0001, + + /// + /// If specified, the key is being released. + /// If not specified, the key is being pressed. + /// + /// + /// + KEYEVENTF_KEYUP = 0x0002, + + /// + /// If specified, identifies the key and is ignored. + /// + /// + /// + KEYEVENTF_SCANCODE = 0x0004, + + /// + /// If specified, the system synthesizes a VK_PACKET keystroke. + /// The parameter must be zero. + /// This flag can only be combined with the flag. + /// + /// + /// + KEYEVENTF_UNICODE = 0x0008, +} diff --git a/NibblePoker.Win32Bindings/WinUser/SendKeys/EMouseEventFlags.cs b/NibblePoker.Win32Bindings/WinUser/SendKeys/EMouseEventFlags.cs new file mode 100644 index 0000000..97e6a7f --- /dev/null +++ b/NibblePoker.Win32Bindings/WinUser/SendKeys/EMouseEventFlags.cs @@ -0,0 +1,114 @@ +namespace NibblePoker.Win32Bindings.WinUser.SendKeys; + +// ReSharper disable InconsistentNaming +[Flags] +public enum EMouseEventFlags : uint { + // https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-MOUSEINPUT + + /// + /// Movement occurred. + /// + /// + /// + MOUSEEVENTF_MOVE = 0x0001, + + /// + /// The left button was pressed. + /// + /// + /// + MOUSEEVENTF_LEFTDOWN = 0x0002, + + /// + /// The left button was released. + /// + /// + /// + MOUSEEVENTF_LEFTUP = 0x0004, + + /// + /// The right button was pressed. + /// + /// + /// + MOUSEEVENTF_RIGHTDOWN = 0x0008, + + /// + /// The right button was released. + /// + /// + /// + MOUSEEVENTF_RIGHTUP = 0x0010, + + /// + /// The middle button was pressed. + /// + /// + /// + MOUSEEVENTF_MIDDLEDOWN = 0x0020, + + /// + /// The middle button was released. + /// + /// + /// + MOUSEEVENTF_MIDDLEUP = 0x0040, + + /// + /// An X button was pressed. + /// + /// + /// + MOUSEEVENTF_XDOWN = 0x0080, + + /// + /// An X button was released. + /// + /// + /// + MOUSEEVENTF_XUP = 0x0100, + + /// + /// The wheel was moved, if the mouse has a wheel. + /// The amount of movement is specified in mouseData. + /// + /// + /// + MOUSEEVENTF_WHEEL = 0x0800, + + /// + /// The wheel was moved horizontally, if the mouse has a wheel. + /// The amount of movement is specified in mouseData. + /// + /// Windows XP/2000: This value is not supported + /// + /// + MOUSEEVENTF_HWHEEL = 0x1000, + + /// + /// The WM_MOUSEMOVE + /// messages will not be coalesced. + /// The default behavior is to coalesce WM_MOUSEMOVE messages. + /// + /// Windows XP/2000: This value is not supported + /// + /// + MOUSEEVENTF_MOVE_NOCOALESCE = 0x2000, + + /// + /// Maps coordinates to the entire desktop. + /// Must be used with . + /// + /// + /// + MOUSEEVENTF_VIRTUALDESK = 0x4000, + + /// + /// The dx and dy members contain normalized absolute coordinates. + /// If the flag is not set, `dx` and `dy` contain relative data (the change in position since the last reported position). + /// This flag can be set, or not set, regardless of what kind of mouse or other pointing device, if any, is connected to the system. + /// + /// + /// + MOUSEEVENTF_ABSOLUTE = 0x8000, +} diff --git a/NibblePoker.Win32Bindings/WinUser/SendKeys/Imports.cs b/NibblePoker.Win32Bindings/WinUser/SendKeys/Imports.cs new file mode 100644 index 0000000..76a5c47 --- /dev/null +++ b/NibblePoker.Win32Bindings/WinUser/SendKeys/Imports.cs @@ -0,0 +1,8 @@ +using System.Runtime.InteropServices; + +namespace NibblePoker.Win32Bindings.WinUser.SendKeys; + +public class Imports { + [DllImport("user32.dll", CallingConvention = CallingConvention.Winapi)] + public static extern uint SendInput(uint nInputs, Structures.INPUT[] pInputs, int cbSize); +} diff --git a/NibblePoker.Win32Bindings/WinUser/SendKeys/Structures.cs b/NibblePoker.Win32Bindings/WinUser/SendKeys/Structures.cs new file mode 100644 index 0000000..d5f554d --- /dev/null +++ b/NibblePoker.Win32Bindings/WinUser/SendKeys/Structures.cs @@ -0,0 +1,44 @@ +using System.Runtime.InteropServices; + +namespace NibblePoker.Win32Bindings.WinUser.SendKeys; + +public class Structures { + [StructLayout(LayoutKind.Sequential)] + public struct INPUT { + public int type; + public InputUnion U; + } + + [StructLayout(LayoutKind.Explicit)] + public struct InputUnion { + [FieldOffset(0)] public MOUSEINPUT mi; + [FieldOffset(0)] public KEYBDINPUT ki; + [FieldOffset(0)] public HARDWAREINPUT hi; + } + + [StructLayout(LayoutKind.Sequential)] + public struct MOUSEINPUT { + public int dx; + public int dy; + public int mouseData; + public uint dwFlags; + public uint time; + public IntPtr dwExtraInfo; + } + + [StructLayout(LayoutKind.Sequential)] + public struct KEYBDINPUT { + public ushort wVk; + public ushort wScan; + public uint dwFlags; + public uint time; + public IntPtr dwExtraInfo; + } + + [StructLayout(LayoutKind.Sequential)] + public struct HARDWAREINPUT { + public uint uMsg; + public ushort wParamL; + public ushort wParamH; + } +} diff --git a/NibblePoker.Win32Wrappers/Beeps.cs b/NibblePoker.Win32Wrappers/Beeps.cs new file mode 100644 index 0000000..a4fcbe5 --- /dev/null +++ b/NibblePoker.Win32Wrappers/Beeps.cs @@ -0,0 +1,45 @@ +using System.ComponentModel; +using System.Runtime.InteropServices; +using NibblePoker.Win32Bindings; + +namespace NibblePoker.Win32Wrappers; + +// ReSharper disable MemberCanBePrivate.Global +public static class Beeps { + public enum SoundTypes : uint { + Fallback = User32.MB_FALLBACK, + CriticalStop = User32.MB_ICONERROR, + Asterisk = User32.MB_ICONINFORMATION, + Question = User32.MB_ICONQUESTION, // Doesn't work on W11 !! + Exclamation = User32.MB_ICONWARNING, + Default = User32.MB_OK, + } + + public static void Beep(uint frequency, uint durationMs) { + if(!Kernel32.Beep(frequency, durationMs)) { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + } + + public static void Beep(uint type) { + if(!User32.MessageBeep(type)) { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + } + + public static void Beep(SoundTypes type) { + Beep((uint)type); + } + + public static bool BeepSafe(uint frequency, uint durationMs) { + return Kernel32.Beep(frequency, durationMs); + } + + public static bool BeepSafe(uint type) { + return User32.MessageBeep(type); + } + + public static bool BeepSafe(SoundTypes type) { + return BeepSafe((uint)type); + } +} diff --git a/NibblePoker.Win32Wrappers/MessageBox.cs b/NibblePoker.Win32Wrappers/MessageBox.cs new file mode 100644 index 0000000..b60f947 --- /dev/null +++ b/NibblePoker.Win32Wrappers/MessageBox.cs @@ -0,0 +1,52 @@ +using System.ComponentModel; +using System.Runtime.InteropServices; +using static NibblePoker.Win32Bindings.User32; + +namespace NibblePoker.Win32Wrappers; + +public static class MessageBox { + // ReSharper disable once EnumUnderlyingTypeIsInt + public enum EResults : int { + Error = 0, + Abort = IDABORT, + Cancel = IDCANCEL, + Continue = IDCONTINUE, + Ignore = IDIGNORE, + No = IDNO, + Ok = IDOK, + Retry = IDRETRY, + TryAgain = IDTRYAGAIN, + Yes = IDYES, + } + + public static EResults Show(string title, string content, uint options = 0) { + nint ptrTitle = Marshal.StringToHGlobalUni(title); + nint ptrContent = Marshal.StringToHGlobalUni(content); + + try { + EResults mbResult = (EResults)MessageBoxW(IntPtr.Zero, ptrContent, ptrTitle, options); + + if(mbResult == EResults.Error) { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + + // TODO: Check if the code is known ! + /*else { +#if DEBUG + Console.WriteLine("Mode=Debug"); +#else + Console.WriteLine("Mode=Release"); +#endif + }*/ + + return mbResult; + } finally { + Marshal.FreeHGlobal(ptrContent); + Marshal.FreeHGlobal(ptrTitle); + } + } + + /*public static int ShowSafe(string title, string content, uint options = 0) { + return 0; + }*/ +} diff --git a/NibblePoker.Win32Wrappers/NibblePoker.Win32Wrappers.csproj b/NibblePoker.Win32Wrappers/NibblePoker.Win32Wrappers.csproj new file mode 100644 index 0000000..7dfab9a --- /dev/null +++ b/NibblePoker.Win32Wrappers/NibblePoker.Win32Wrappers.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/icon.png b/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1a2e2438ef4cecdf556f41f13dadd428ef1c50a4 GIT binary patch literal 7881 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