├── .gitattributes
├── .gitignore
├── App.xaml
├── App.xaml.cs
├── Assets
├── LockScreenLogo.scale-200.png
├── SplashScreen.scale-200.png
├── Square150x150Logo.scale-200.png
├── Square44x44Logo.scale-200.png
├── Square44x44Logo.targetsize-24_altform-unplated.png
├── StoreLogo.png
├── Wide310x150Logo.scale-200.png
├── control.ico
└── control.png
├── CH3929.cs
├── Control3.csproj
├── Control3.sln
├── Globals.cs
├── LICENSE.txt
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── Privacy Policy.txt
├── Properties
└── launchSettings.json
├── README.md
├── Remote.xaml
├── Remote.xaml.cs
├── SetupControl3.exe
├── app.manifest
└── keycodes.xlsx
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Oo]ut/
33 | [Ll]og/
34 | [Ll]ogs/
35 |
36 | # Visual Studio 2015/2017 cache/options directory
37 | .vs/
38 | # Uncomment if you have tasks that create the project's static files in wwwroot
39 | #wwwroot/
40 |
41 | # Visual Studio 2017 auto generated files
42 | Generated\ Files/
43 |
44 | # MSTest test Results
45 | [Tt]est[Rr]esult*/
46 | [Bb]uild[Ll]og.*
47 |
48 | # NUnit
49 | *.VisualState.xml
50 | TestResult.xml
51 | nunit-*.xml
52 |
53 | # Build Results of an ATL Project
54 | [Dd]ebugPS/
55 | [Rr]eleasePS/
56 | dlldata.c
57 |
58 | # Benchmark Results
59 | BenchmarkDotNet.Artifacts/
60 |
61 | # .NET Core
62 | project.lock.json
63 | project.fragment.lock.json
64 | artifacts/
65 |
66 | # ASP.NET Scaffolding
67 | ScaffoldingReadMe.txt
68 |
69 | # StyleCop
70 | StyleCopReport.xml
71 |
72 | # Files built by Visual Studio
73 | *_i.c
74 | *_p.c
75 | *_h.h
76 | *.ilk
77 | *.meta
78 | *.obj
79 | *.iobj
80 | *.pch
81 | *.pdb
82 | *.ipdb
83 | *.pgc
84 | *.pgd
85 | *.rsp
86 | *.sbr
87 | *.tlb
88 | *.tli
89 | *.tlh
90 | *.tmp
91 | *.tmp_proj
92 | *_wpftmp.csproj
93 | *.log
94 | *.vspscc
95 | *.vssscc
96 | .builds
97 | *.pidb
98 | *.svclog
99 | *.scc
100 |
101 | # Chutzpah Test files
102 | _Chutzpah*
103 |
104 | # Visual C++ cache files
105 | ipch/
106 | *.aps
107 | *.ncb
108 | *.opendb
109 | *.opensdf
110 | *.sdf
111 | *.cachefile
112 | *.VC.db
113 | *.VC.VC.opendb
114 |
115 | # Visual Studio profiler
116 | *.psess
117 | *.vsp
118 | *.vspx
119 | *.sap
120 |
121 | # Visual Studio Trace Files
122 | *.e2e
123 |
124 | # TFS 2012 Local Workspace
125 | $tf/
126 |
127 | # Guidance Automation Toolkit
128 | *.gpState
129 |
130 | # ReSharper is a .NET coding add-in
131 | _ReSharper*/
132 | *.[Rr]e[Ss]harper
133 | *.DotSettings.user
134 |
135 | # TeamCity is a build add-in
136 | _TeamCity*
137 |
138 | # DotCover is a Code Coverage Tool
139 | *.dotCover
140 |
141 | # AxoCover is a Code Coverage Tool
142 | .axoCover/*
143 | !.axoCover/settings.json
144 |
145 | # Coverlet is a free, cross platform Code Coverage Tool
146 | coverage*.json
147 | coverage*.xml
148 | coverage*.info
149 |
150 | # Visual Studio code coverage results
151 | *.coverage
152 | *.coveragexml
153 |
154 | # NCrunch
155 | _NCrunch_*
156 | .*crunch*.local.xml
157 | nCrunchTemp_*
158 |
159 | # MightyMoose
160 | *.mm.*
161 | AutoTest.Net/
162 |
163 | # Web workbench (sass)
164 | .sass-cache/
165 |
166 | # Installshield output folder
167 | [Ee]xpress/
168 |
169 | # DocProject is a documentation generator add-in
170 | DocProject/buildhelp/
171 | DocProject/Help/*.HxT
172 | DocProject/Help/*.HxC
173 | DocProject/Help/*.hhc
174 | DocProject/Help/*.hhk
175 | DocProject/Help/*.hhp
176 | DocProject/Help/Html2
177 | DocProject/Help/html
178 |
179 | # Click-Once directory
180 | publish/
181 |
182 | # Publish Web Output
183 | *.[Pp]ublish.xml
184 | *.azurePubxml
185 | # Note: Comment the next line if you want to checkin your web deploy settings,
186 | # but database connection strings (with potential passwords) will be unencrypted
187 | *.pubxml
188 | *.publishproj
189 |
190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
191 | # checkin your Azure Web App publish settings, but sensitive information contained
192 | # in these scripts will be unencrypted
193 | PublishScripts/
194 |
195 | # NuGet Packages
196 | *.nupkg
197 | # NuGet Symbol Packages
198 | *.snupkg
199 | # The packages folder can be ignored because of Package Restore
200 | **/[Pp]ackages/*
201 | # except build/, which is used as an MSBuild target.
202 | !**/[Pp]ackages/build/
203 | # Uncomment if necessary however generally it will be regenerated when needed
204 | #!**/[Pp]ackages/repositories.config
205 | # NuGet v3's project.json files produces more ignorable files
206 | *.nuget.props
207 | *.nuget.targets
208 |
209 | # Microsoft Azure Build Output
210 | csx/
211 | *.build.csdef
212 |
213 | # Microsoft Azure Emulator
214 | ecf/
215 | rcf/
216 |
217 | # Windows Store app package directories and files
218 | AppPackages/
219 | BundleArtifacts/
220 | Package.StoreAssociation.xml
221 | _pkginfo.txt
222 | *.appx
223 | *.appxbundle
224 | *.appxupload
225 |
226 | # Visual Studio cache files
227 | # files ending in .cache can be ignored
228 | *.[Cc]ache
229 | # but keep track of directories ending in .cache
230 | !?*.[Cc]ache/
231 |
232 | # Others
233 | ClientBin/
234 | ~$*
235 | *~
236 | *.dbmdl
237 | *.dbproj.schemaview
238 | *.jfm
239 | *.pfx
240 | *.publishsettings
241 | orleans.codegen.cs
242 |
243 | # Including strong name files can present a security risk
244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
245 | #*.snk
246 |
247 | # Since there are multiple workflows, uncomment next line to ignore bower_components
248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
249 | #bower_components/
250 |
251 | # RIA/Silverlight projects
252 | Generated_Code/
253 |
254 | # Backup & report files from converting an old project file
255 | # to a newer Visual Studio version. Backup files are not needed,
256 | # because we have git ;-)
257 | _UpgradeReport_Files/
258 | Backup*/
259 | UpgradeLog*.XML
260 | UpgradeLog*.htm
261 | ServiceFabricBackup/
262 | *.rptproj.bak
263 |
264 | # SQL Server files
265 | *.mdf
266 | *.ldf
267 | *.ndf
268 |
269 | # Business Intelligence projects
270 | *.rdl.data
271 | *.bim.layout
272 | *.bim_*.settings
273 | *.rptproj.rsuser
274 | *- [Bb]ackup.rdl
275 | *- [Bb]ackup ([0-9]).rdl
276 | *- [Bb]ackup ([0-9][0-9]).rdl
277 |
278 | # Microsoft Fakes
279 | FakesAssemblies/
280 |
281 | # GhostDoc plugin setting file
282 | *.GhostDoc.xml
283 |
284 | # Node.js Tools for Visual Studio
285 | .ntvs_analysis.dat
286 | node_modules/
287 |
288 | # Visual Studio 6 build log
289 | *.plg
290 |
291 | # Visual Studio 6 workspace options file
292 | *.opt
293 |
294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
295 | *.vbw
296 |
297 | # Visual Studio LightSwitch build output
298 | **/*.HTMLClient/GeneratedArtifacts
299 | **/*.DesktopClient/GeneratedArtifacts
300 | **/*.DesktopClient/ModelManifest.xml
301 | **/*.Server/GeneratedArtifacts
302 | **/*.Server/ModelManifest.xml
303 | _Pvt_Extensions
304 |
305 | # Paket dependency manager
306 | .paket/paket.exe
307 | paket-files/
308 |
309 | # FAKE - F# Make
310 | .fake/
311 |
312 | # CodeRush personal settings
313 | .cr/personal
314 |
315 | # Python Tools for Visual Studio (PTVS)
316 | __pycache__/
317 | *.pyc
318 |
319 | # Cake - Uncomment if you are using it
320 | # tools/**
321 | # !tools/packages.config
322 |
323 | # Tabs Studio
324 | *.tss
325 |
326 | # Telerik's JustMock configuration file
327 | *.jmconfig
328 |
329 | # BizTalk build output
330 | *.btp.cs
331 | *.btm.cs
332 | *.odx.cs
333 | *.xsd.cs
334 |
335 | # OpenCover UI analysis results
336 | OpenCover/
337 |
338 | # Azure Stream Analytics local run output
339 | ASALocalRun/
340 |
341 | # MSBuild Binary and Structured Log
342 | *.binlog
343 |
344 | # NVidia Nsight GPU debugger configuration file
345 | *.nvuser
346 |
347 | # MFractors (Xamarin productivity tool) working folder
348 | .mfractor/
349 |
350 | # Local History for Visual Studio
351 | .localhistory/
352 |
353 | # BeatPulse healthcheck temp database
354 | healthchecksdb
355 |
356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
357 | MigrationBackup/
358 |
359 | # Ionide (cross platform F# VS Code tools) working folder
360 | .ionide/
361 |
362 | # Fody - auto-generated XML schema
363 | FodyWeavers.xsd
--------------------------------------------------------------------------------
/App.xaml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.UI.Xaml;
2 | using System.Runtime.InteropServices;
3 |
4 | // To learn more about WinUI, the WinUI project structure,
5 | // and more about our project templates, see: http://aka.ms/winui-project-info.
6 |
7 | namespace Control3
8 | {
9 | ///
10 | /// Provides application-specific behavior to supplement the default Application class.
11 | ///
12 | public partial class App : Application
13 | {
14 | ///
15 | /// Initializes the singleton application object. This is the first line of authored code
16 | /// executed, and as such is the logical equivalent of main() or WinMain().
17 | ///
18 | ///
19 | public App()
20 | {
21 | this.InitializeComponent();
22 | }
23 |
24 | ///
25 | /// Invoked when the application is launched.
26 | ///
27 | /// Details about the launch request and process.
28 | protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
29 | {
30 | m_window = new MainWindow();
31 | m_window.Activate();
32 | }
33 |
34 | private Window m_window;
35 |
36 | // --------- Global Variables Initialization -----------
37 | // Variables to be globally used in this projects classes and windows
38 | public static Flags Flag { get; } = new Flags();
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/Assets/LockScreenLogo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sipper69/Control3/b54ced6eb02490892561bf0a3d11767c79e0c335/Assets/LockScreenLogo.scale-200.png
--------------------------------------------------------------------------------
/Assets/SplashScreen.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sipper69/Control3/b54ced6eb02490892561bf0a3d11767c79e0c335/Assets/SplashScreen.scale-200.png
--------------------------------------------------------------------------------
/Assets/Square150x150Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sipper69/Control3/b54ced6eb02490892561bf0a3d11767c79e0c335/Assets/Square150x150Logo.scale-200.png
--------------------------------------------------------------------------------
/Assets/Square44x44Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sipper69/Control3/b54ced6eb02490892561bf0a3d11767c79e0c335/Assets/Square44x44Logo.scale-200.png
--------------------------------------------------------------------------------
/Assets/Square44x44Logo.targetsize-24_altform-unplated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sipper69/Control3/b54ced6eb02490892561bf0a3d11767c79e0c335/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
--------------------------------------------------------------------------------
/Assets/StoreLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sipper69/Control3/b54ced6eb02490892561bf0a3d11767c79e0c335/Assets/StoreLogo.png
--------------------------------------------------------------------------------
/Assets/Wide310x150Logo.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sipper69/Control3/b54ced6eb02490892561bf0a3d11767c79e0c335/Assets/Wide310x150Logo.scale-200.png
--------------------------------------------------------------------------------
/Assets/control.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sipper69/Control3/b54ced6eb02490892561bf0a3d11767c79e0c335/Assets/control.ico
--------------------------------------------------------------------------------
/Assets/control.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sipper69/Control3/b54ced6eb02490892561bf0a3d11767c79e0c335/Assets/control.png
--------------------------------------------------------------------------------
/CH3929.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO.Ports; // NuGet -> System.IO.Ports 8.0.0
3 | using System.Linq;
4 | using System.Threading;
5 |
6 | namespace Control3
7 | {
8 | public class CH9329
9 | {
10 | // This class is a modified version of the class provided by SmallCodeNote under MIT License
11 | // https://github.com/SmallCodeNote/CH9329-109KeyClass
12 |
13 | public string PortName;
14 | public int BaudRate;
15 | public int LeftStatus = 0;
16 | private SerialPort serialPort;
17 |
18 | public CH9329(string PortName = "COM8", int BaudRate = 9600)
19 | {
20 | this.PortName = PortName;
21 | this.BaudRate = BaudRate;
22 |
23 | serialPort = new SerialPort(PortName, BaudRate);
24 | serialPort.Open();
25 |
26 | createMediaKeyTable();
27 | }
28 |
29 | private Dictionary mediaKeyTable;
30 |
31 | public enum mediaKey
32 | {
33 | EJECT,
34 | CDSTOP,
35 | PREVTRACK,
36 | NEXTTRACK,
37 | PLAYPAUSE,
38 | MUTE,
39 | VOLUMEDOWN,
40 | VOLUMEUP,
41 | }
42 |
43 | private void createMediaKeyTable()
44 | {
45 | mediaKeyTable = new Dictionary();
46 | mediaKeyTable.Add(mediaKey.EJECT, new byte[] { 0x02, 0x80, 0x00, 0x00 });
47 | mediaKeyTable.Add(mediaKey.CDSTOP, new byte[] { 0x02, 0x40, 0x00, 0x00 });
48 | mediaKeyTable.Add(mediaKey.PREVTRACK, new byte[] { 0x02, 0x20, 0x00, 0x00 });
49 | mediaKeyTable.Add(mediaKey.NEXTTRACK, new byte[] { 0x02, 0x10, 0x00, 0x00 });
50 | mediaKeyTable.Add(mediaKey.PLAYPAUSE, new byte[] { 0x02, 0x08, 0x00, 0x00 });
51 | mediaKeyTable.Add(mediaKey.MUTE, new byte[] { 0x02, 0x04, 0x00, 0x00 });
52 | mediaKeyTable.Add(mediaKey.VOLUMEDOWN, new byte[] { 0x02, 0x02, 0x00, 0x00 });
53 | mediaKeyTable.Add(mediaKey.VOLUMEUP, new byte[] { 0x02, 0x01, 0x00, 0x00 });
54 | }
55 |
56 | public enum SpecialKeyCode : byte
57 | {
58 | ENTER = 0x28,
59 | ESCAPE = 0x29,
60 | BACKSPACE = 0x2A,
61 | TAB = 0x2B,
62 | SPACEBAR = 0x2C,
63 | CAPS_LOCK = 0x39,
64 | F1 = 0x3A,
65 | F2 = 0x3B,
66 | F3 = 0x3C,
67 | F4 = 0x3D,
68 | F5 = 0x3E,
69 | F6 = 0x3F,
70 | F7 = 0x40,
71 | F8 = 0x41,
72 | F9 = 0x42,
73 | F10 = 0x43,
74 | F11 = 0x44,
75 | F12 = 0x45,
76 | PRINTSCREEN = 0x46,
77 | SCROLL_LOCK = 0x47,
78 | PAUSE = 0x48,
79 | INSERT = 0x49,
80 | HOME = 0x4A,
81 | PAGEUP = 0x4B,
82 | DELETE = 0x4C,
83 | END = 0x4D,
84 | PAGEDOWN = 0x4E,
85 | RIGHTARROW = 0x4F,
86 | LEFTARROW = 0x50,
87 | DOWNARROW = 0x51,
88 | UPARROW = 0x52,
89 | APPLICATION = 0x65,
90 | LEFT_CTRL = 0xE0,
91 | LEFT_SHIFT = 0xE1,
92 | LEFT_ALT = 0xE2,
93 | LEFT_WINDOWS = 0xE3,
94 | RIGHT_CTRL = 0xE4,
95 | RIGHT_SHIFT = 0xE5,
96 | RIGHT_ALT = 0xE6,
97 | RIGHT_WINDOWS = 0xE7,
98 | CTRL = 0xE4,
99 | SHIFT = 0xE5,
100 | ALT = 0xE6,
101 | WINDOWS = 0xE7,
102 | }
103 |
104 | public enum MouseButtonCode : byte
105 | {
106 | LEFT = 0x01,
107 | RIGHT = 0x02,
108 | MIDDLE = 0x04,
109 | }
110 |
111 | private string sendPacket(byte[] data)
112 | {
113 | string resultMessage = "";
114 |
115 | // Use a separate thread for serial port communication
116 | Thread serialThread = new Thread(() =>
117 | {
118 | serialPort.Write(data, 0, data.Length);
119 | Thread.Sleep(1);
120 |
121 | // Unset isMoving (see GlobalHook_MouseMove in MainWindow)
122 | // In CH9329 the flag is set to false again after the package is sent to remote. Prevents queue of movement on remote
123 | App.Flag.isMoving = false;
124 | });
125 | serialThread.Start();
126 |
127 | return resultMessage;
128 | }
129 |
130 | private byte[] createPacketArray(List arrList, bool addCheckSum)
131 | {
132 | List bytePacketList = arrList.ConvertAll(b => (byte)b);
133 | if (addCheckSum) bytePacketList.Add((byte)(arrList.Sum() & 0xff));
134 | return bytePacketList.ToArray();
135 | }
136 |
137 | byte[] charKeyUpPacket = { 0x57, 0xAB, 0x00, 0x02, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c };
138 | byte[] mediaKeyUpPacket = { 0x57, 0xAB, 0x00, 0x03, 0x04, 0x02, 0x00, 0x00, 0x00, 0x0B };
139 |
140 | public enum KeyGroup : byte
141 | {
142 | CharKey = 0x02,
143 | MediaKey = 0x03,
144 | }
145 | public enum CommandCode : byte
146 | {
147 | GET_INFO = 0x01,
148 | SEND_KB_GENERAL_DATA = 0x02,
149 | SEND_KB_MEDIA_DATA = 0x03,
150 | SEND_MS_ABS_DATA = 0x04,
151 | SEND_MS_REL_DATA = 0x05,
152 | READ_MY_HID_DATA = 0x07,
153 | GET_PARA_CFG = 0x08,
154 | GET_USB_STRING = 0x0A,
155 | }
156 |
157 | public byte CHIP_VERSION;
158 | public byte CHIP_STATUS;
159 | public bool NUM_LOCK;
160 | public bool CAPS_LOCK;
161 | public bool SCROLL_LOCK;
162 |
163 | public void getInfo()
164 | {
165 | byte[] getInfoPacket = { 0x57, 0xAB, 0x00, (byte)CommandCode.GET_INFO, 0x00, 0x03 };
166 | string resultString = sendPacket(getInfoPacket);
167 |
168 | CHIP_VERSION = (byte)resultString[0];
169 | CHIP_STATUS = (byte)resultString[1];
170 | byte flagByte = (byte)resultString[2];
171 | NUM_LOCK = (flagByte & 0b00000001) > 0;
172 | CAPS_LOCK = (flagByte & 0b00000010) > 0;
173 | SCROLL_LOCK = (flagByte & 0b00000100) > 0;
174 | }
175 |
176 | public void keyDown(KeyGroup keyGroup, byte k0, byte k1, byte k2 = 0, byte k3 = 0, byte k4 = 0, byte k5 = 0, byte k6 = 0)
177 | {
178 | // ========================
179 | // keyDownPacketContents
180 | // HEAD{0x57, 0xAB} + ADDR{0x00} + CMD{0x02} + LEN{0x08} + DATA{k0, 0x00, k1, k2, k3, k4, k5, k6}
181 | // CMD = KeyGroup
182 | // ========================
183 | List keyDownPacketListInt = new List { 0x57, 0xAB, 0x00, (int)keyGroup, 0x08, k0, 0x00, k1, k2, k3, k4, k5, k6 };
184 |
185 | byte[] keyDownPacket = createPacketArray(keyDownPacketListInt, true);
186 |
187 | sendPacket(keyDownPacket);
188 | }
189 | public void keyUpAll()
190 | {
191 | keyUpAll(KeyGroup.CharKey);
192 | }
193 | public void keyUpAll(KeyGroup keyGroup)
194 | {
195 | if (keyGroup == KeyGroup.CharKey) { sendPacket(charKeyUpPacket); }
196 | else { sendPacket(mediaKeyUpPacket); };
197 | }
198 | public void keyDown(SpecialKeyCode specialKeyCode)
199 | {
200 | keyDown(KeyGroup.CharKey, (byte)specialKeyCode, 0x00);
201 | }
202 | public void charKeyType(byte k0, byte k1, byte k2 = 0, byte k3 = 0, byte k4 = 0, byte k5 = 0, byte k6 = 0)
203 | {
204 | keyDown(KeyGroup.CharKey, k0, k1, k2, k3, k4, k5, k6);
205 | keyUpAll(KeyGroup.CharKey);
206 | }
207 |
208 | public void mediaKeyType(mediaKey MediaKey)
209 | {
210 | byte[] dat = mediaKeyTable[MediaKey];
211 | keyDown(KeyGroup.MediaKey, dat[0], dat[1], dat[2], dat[3]);
212 | keyUpAll(KeyGroup.MediaKey);
213 | }
214 |
215 | public void mouseMoveRel(int x, int y)
216 | {
217 | if (x > 127) { x = 127; }; if (x < -128) { x = -128; }; if (x < 0) { x = 0x100 + x; };
218 | if (y > 127) { y = 127; }; if (y < -128) { y = -128; }; if (y < 0) { y = 0x100 + y; };
219 |
220 | // ========================
221 | // mouseMoveRelPacketContents
222 | // HEAD{0x57, 0xAB} + ADDR{0x00} + CMD{0x05} + LEN{0x05} + DATA{0x01, 0x00}
223 | // CMD = 0x05 : USB mouse relative mode
224 | // ========================
225 | List mouseMoveRelPacketListInt = new List { 0x57, 0xAB, 0x00, 0x05, 0x05, 0x01, 0x00 };
226 |
227 | mouseMoveRelPacketListInt[6] = LeftStatus;
228 |
229 | mouseMoveRelPacketListInt.Add((byte)(x));
230 | mouseMoveRelPacketListInt.Add((byte)(y));
231 | mouseMoveRelPacketListInt.Add(0x00);
232 |
233 | byte[] mouseMoveRelPacket = createPacketArray(mouseMoveRelPacketListInt, true);
234 | sendPacket(mouseMoveRelPacket);
235 | }
236 |
237 | public void mouseButtonDown(MouseButtonCode buttonCode)
238 | {
239 | // ========================
240 | // mouseClickPacketContents
241 | // HEAD{0x57, 0xAB} + ADDR{0x00} + CMD{0x05} + LEN{0x05} + DATA{0x01}
242 | // CMD = 0x05 : USB mouse relative mode
243 | // ========================
244 | List mouseButtonDownPacketListInt = new List { 0x57, 0xAB, 0x00, 0x05, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00 };
245 | mouseButtonDownPacketListInt[6] = (int)buttonCode;
246 |
247 | if ((int)buttonCode == 1)
248 | {
249 | LeftStatus = 1;
250 | }
251 |
252 | byte[] mouseButtonDownPacket = createPacketArray(mouseButtonDownPacketListInt, true);
253 | sendPacket(mouseButtonDownPacket);
254 | }
255 |
256 | public void mouseButtonUpAll()
257 | {
258 | byte[] mouseButtonUpPacket = { 0x57, 0xAB, 0x00, 0x05, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0D };
259 | LeftStatus = 0;
260 | sendPacket(mouseButtonUpPacket);
261 | }
262 |
263 | public string mouseScroll(int scrollCount)
264 | {
265 | // ========================
266 | // mouseScrollPacketContents
267 | // HEAD{0x57, 0xAB} + ADDR{0x00} + CMD{0x05} + LEN{0x05} + DATA{0x01}
268 | // CMD = 0x05 : USB mouse relative mode
269 | // ========================
270 |
271 | List mouseScrollPacketListInt = new List { 0x57, 0xAB, 0x00, 0x05, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00 };
272 | mouseScrollPacketListInt[9] = scrollCount;
273 | //mouseScrollPacketListInt.Add(scrollCount);
274 |
275 | byte[] mouseScrollPacket = createPacketArray(mouseScrollPacketListInt, true);
276 | return sendPacket(mouseScrollPacket);
277 | }
278 | }
279 | }
280 |
--------------------------------------------------------------------------------
/Control3.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | WinExe
4 | net6.0-windows10.0.19041.0
5 | 10.0.17763.0
6 | app.manifest
7 | x86;x64
8 | win10-x86;win10-x64
9 | true
10 | true
11 |
14 | None
15 | true
16 | Assets\control.ico
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/Control3.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.9.34622.214
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Control3", "Control3.csproj", "{B4546E4B-099F-407B-ADF5-062E84AC017F}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|ARM64 = Debug|ARM64
11 | Debug|x64 = Debug|x64
12 | Debug|x86 = Debug|x86
13 | Release|ARM64 = Release|ARM64
14 | Release|x64 = Release|x64
15 | Release|x86 = Release|x86
16 | EndGlobalSection
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 | {B4546E4B-099F-407B-ADF5-062E84AC017F}.Debug|ARM64.ActiveCfg = Debug|ARM64
19 | {B4546E4B-099F-407B-ADF5-062E84AC017F}.Debug|ARM64.Build.0 = Debug|ARM64
20 | {B4546E4B-099F-407B-ADF5-062E84AC017F}.Debug|ARM64.Deploy.0 = Debug|ARM64
21 | {B4546E4B-099F-407B-ADF5-062E84AC017F}.Debug|x64.ActiveCfg = Debug|x64
22 | {B4546E4B-099F-407B-ADF5-062E84AC017F}.Debug|x64.Build.0 = Debug|x64
23 | {B4546E4B-099F-407B-ADF5-062E84AC017F}.Debug|x64.Deploy.0 = Debug|x64
24 | {B4546E4B-099F-407B-ADF5-062E84AC017F}.Debug|x86.ActiveCfg = Debug|x86
25 | {B4546E4B-099F-407B-ADF5-062E84AC017F}.Debug|x86.Build.0 = Debug|x86
26 | {B4546E4B-099F-407B-ADF5-062E84AC017F}.Debug|x86.Deploy.0 = Debug|x86
27 | {B4546E4B-099F-407B-ADF5-062E84AC017F}.Release|ARM64.ActiveCfg = Release|ARM64
28 | {B4546E4B-099F-407B-ADF5-062E84AC017F}.Release|ARM64.Build.0 = Release|ARM64
29 | {B4546E4B-099F-407B-ADF5-062E84AC017F}.Release|ARM64.Deploy.0 = Release|ARM64
30 | {B4546E4B-099F-407B-ADF5-062E84AC017F}.Release|x64.ActiveCfg = Release|x64
31 | {B4546E4B-099F-407B-ADF5-062E84AC017F}.Release|x64.Build.0 = Release|x64
32 | {B4546E4B-099F-407B-ADF5-062E84AC017F}.Release|x64.Deploy.0 = Release|x64
33 | {B4546E4B-099F-407B-ADF5-062E84AC017F}.Release|x86.ActiveCfg = Release|x86
34 | {B4546E4B-099F-407B-ADF5-062E84AC017F}.Release|x86.Build.0 = Release|x86
35 | {B4546E4B-099F-407B-ADF5-062E84AC017F}.Release|x86.Deploy.0 = Release|x86
36 | EndGlobalSection
37 | GlobalSection(SolutionProperties) = preSolution
38 | HideSolutionNode = FALSE
39 | EndGlobalSection
40 | GlobalSection(ExtensibilityGlobals) = postSolution
41 | SolutionGuid = {CEC768F4-6085-4201-B86D-FBA5A88E585A}
42 | EndGlobalSection
43 | EndGlobal
44 |
--------------------------------------------------------------------------------
/Globals.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualBasic.ApplicationServices;
2 | using Microsoft.Win32;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Diagnostics;
6 | using System.Management; // NuGet -> System.Management 8.0.0
7 | using System.Runtime.InteropServices;
8 | using System.Windows.Forms;
9 |
10 | namespace Control3
11 | {
12 | public class VideoSourceInfo
13 | {
14 | public string Name { get; set; }
15 | public string Id { get; set; }
16 | }
17 | // Class GlobalVars for variables to be globally used in this projects classes and windows
18 | public class Flags
19 | {
20 | // The videosource to be used for the remote
21 | public string selectedSource = null;
22 |
23 | // Is the mouse in motion? Wait to send the next motion, else you get a queue of movement which continues remote
24 | // Now it just skips movements when the serialport is not ready yet
25 | // This global is set at a MouseMove being captured and unnset once the the packet is processed by the CH3929
26 | public Boolean isMoving = false;
27 |
28 | // True meanse that all the mouse and keyboard actions are processed by the globalhook routines
29 | // and not send through to the local desktop!!
30 | public Boolean isRemote = false;
31 |
32 | // Is the remote window started in FullScreen
33 | public Boolean isFullScreen = false;
34 |
35 | //The Decoration variable is used to send the extra keys which are pressed while typing
36 | //bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0
37 | //Right Windows Right Alt Right Shift Right Ctrl Left Windows Left Alt Left Shift Left Ctrl
38 | public byte Decoration = 0;
39 |
40 | //COM Port for the remote cable
41 | public string COMPort = null;
42 |
43 | //The Keymap is used to translate the Keycode to the CH9329 codes
44 | public static Dictionary KeyMap { get; private set; }
45 | static Flags()
46 | {
47 | KeyMap = new Dictionary
48 | {
49 | { 8, 0x2A }, // Back
50 | { 9, 0x2B }, // Tab
51 | { 13, 0x28 }, // Enter
52 | { 19, 0x48 }, //Pause
53 | { 20, 0x39 }, //Caps Lock
54 | { 27, 0x29 }, //Escape
55 | { 32, 0x2C }, // Space
56 | { 33, 0x4B }, // PageUp
57 | { 34, 0x4E }, // Next
58 | { 35, 0x4D }, // End
59 | { 36, 0x4A }, // Home
60 | { 37, 0x50 }, // Left
61 | { 38, 0x52 }, // Up
62 | { 39, 0x4F }, // Right
63 | { 40, 0x51 }, // Down
64 | { 44, 0x46 }, // PrintScreen
65 | { 45, 0x49 }, // Insert
66 | { 46, 0x4C }, // Delete
67 | { 48, 0x27 }, // 0
68 | { 49, 0x1E }, // 1
69 | { 50, 0x1F }, // 2
70 | { 51, 0x20 }, // 3
71 | { 52, 0x21 }, // 4
72 | { 53, 0x22 }, // 5
73 | { 54, 0x23 }, // 6
74 | { 55, 0x24 }, // 7
75 | { 56, 0x25 }, // 8
76 | { 57, 0x26 }, // 9
77 | { 65, 0x04 }, // A
78 | { 66, 0x05 }, // B
79 | { 67, 0x06 }, // C
80 | { 68, 0x07 }, // D
81 | { 69, 0x08 }, // E
82 | { 70, 0x09 }, // F
83 | { 71, 0x0A }, // G
84 | { 72, 0x0B }, // H
85 | { 73, 0x0C }, // I
86 | { 74, 0x0D }, // J
87 | { 75, 0x0E }, // K
88 | { 76, 0x0F }, // L
89 | { 77, 0x10 }, // M
90 | { 78, 0x11 }, // N
91 | { 79, 0x12 }, // O
92 | { 80, 0x13 }, // P
93 | { 81, 0x14 }, // Q
94 | { 82, 0x15 }, // R
95 | { 83, 0x16 }, // S
96 | { 84, 0x17 }, // T
97 | { 85, 0x18 }, // U
98 | { 86, 0x19 }, // V
99 | { 87, 0x1A }, // W
100 | { 88, 0x1B }, // X
101 | { 89, 0x1C }, // Y
102 | { 90, 0x1D }, // Z
103 | { 91, 0xE3 }, // LWin
104 | { 93, 0x65 }, // Applications
105 | { 96, 0x62 }, // NumPad 0
106 | { 97, 0x59 }, // NumPad 1
107 | { 98, 0x5A }, // NumPad 2
108 | { 99, 0x5B }, // NumPad 3
109 | { 100, 0x5C }, // NumPad 4
110 | { 101, 0x5D }, // NumPad 5
111 | { 102, 0x5E }, // NumPad 6
112 | { 103, 0x5F }, // NumPad 7
113 | { 104, 0x60 }, // NumPad 8
114 | { 105, 0x61 }, // NumPad 9
115 | { 106, 0x55 }, // NumPad Multiply
116 | { 107, 0x57 }, // NumPad Add
117 | { 109, 0x56 }, // NumPad Subtract
118 | { 110, 0x63 }, // NumPad Decimal
119 | { 111, 0x54 }, // NumPad Divide
120 | { 112, 0x3A }, // F1
121 | { 113, 0x3B }, // F2
122 | { 114, 0x3C }, // F3
123 | { 115, 0x3D }, // F4
124 | { 116, 0x3E }, // F5
125 | { 117, 0x3F }, // F6
126 | { 118, 0x40 }, // F7
127 | { 119, 0x41 }, // F8
128 | { 120, 0x42 }, // F9
129 | { 121, 0x43 }, // F10
130 | { 122, 0x44 }, // F11
131 | { 123, 0x45 }, // F12
132 | { 144, 0x53 }, // NumLock
133 | { 160, 0xE1 }, // LShiftKey
134 | { 161, 0xE5 }, // RShiftKey
135 | { 162, 0xE0 }, // LControlKey
136 | { 163, 0xE4 }, // RControlKey
137 | { 164, 0xE2 }, // LAlt
138 | { 165, 0xE6 }, // Ralt
139 | { 186, 0x33 }, // ;
140 | { 187, 0x2E }, // =
141 | { 188, 0x36 }, // ,
142 | { 189, 0x2D }, // -
143 | { 190, 0x37 }, // .
144 | { 191, 0x38 }, // /
145 | { 192, 0x35 }, // `
146 | { 219, 0x2F }, // [
147 | { 220, 0x31 }, // \
148 | { 221, 0x30 }, // ]
149 | { 222, 0x34 } // '
150 | };
151 | }
152 | }
153 | public static class MouseUtility
154 | {
155 | [DllImport("user32.dll")]
156 | [return: MarshalAs(UnmanagedType.Bool)]
157 | private static extern bool SetCursorPos(int X, int Y);
158 | public static void SetMouseCursorAside()
159 | {
160 | Screen primaryScreen = Screen.PrimaryScreen;
161 | int maxX = primaryScreen.Bounds.Width;
162 | int maxY = primaryScreen.Bounds.Height/2;
163 | SetCursorPos(maxX, maxY);
164 | }
165 | }
166 | public static class SerialPortUtility
167 | {
168 | public static Dictionary GetSerialPorts()
169 | {
170 | Dictionary serialPorts = new Dictionary();
171 | ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT * FROM Win32_PnPEntity WHERE Caption LIKE '%(COM%'");
172 | foreach (ManagementObject queryObj in searcher.Get())
173 | {
174 | string caption = queryObj["Caption"].ToString();
175 | string device = queryObj["DeviceID"].ToString();
176 | int comIndex = caption.IndexOf("(COM");
177 | if (comIndex >= 0)
178 | {
179 | string portName = caption.Substring(comIndex + 1, caption.IndexOf(")", comIndex) - comIndex - 1);
180 | serialPorts.Add(portName, device);
181 | }
182 | }
183 | return serialPorts;
184 | }
185 | }
186 | public class SettingsManager
187 | {
188 | public static void SaveSetting(string value)
189 | {
190 | string companyName = "Result3";
191 | string applicationName = "Control3";
192 | string settingName = "VideoSource";
193 | RegistryKey key = Registry.CurrentUser.CreateSubKey($"Software\\{companyName}\\{applicationName}");
194 | key.SetValue(settingName, value);
195 | key.Close();
196 | }
197 | public static string GetSetting()
198 | {
199 | string companyName = "Result3";
200 | string applicationName = "Control3";
201 | string settingName = "VideoSource";
202 | RegistryKey readKey = Registry.CurrentUser.OpenSubKey($"Software\\{companyName}\\{applicationName}");
203 | string readValue = readKey?.GetValue(settingName)?.ToString();
204 | readKey?.Close();
205 | return readValue as string;
206 | }
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) [year] [fullname]
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.UI; // NuGet -> Microsoft.Windows.SDK.BuildTools & Microsoft.WindowsAppSDK
2 | using Microsoft.UI.Xaml;
3 | using Microsoft.UI.Xaml.Controls;
4 | using Microsoft.UI.Xaml.Media;
5 | using SharpDX.DirectInput; // NuGet -> SharpDX.DirectInput 4.2.0
6 | using Gma.System.MouseKeyHook; // Nuget -> MouseKeyHook 5.7.1
7 | using System;
8 | using System.Collections.Generic;
9 | using System.Linq;
10 | using Windows.Devices.Enumeration;
11 | using System.Diagnostics;
12 |
13 | namespace Control3
14 | {
15 | public sealed partial class MainWindow : Window
16 | {
17 | public static TextBlock MessageControl { get; set; } // Object to be able to set a message from other windows
18 | private Remote remoteInstance = null; // Create object for remote window so we can open/close it from the main window
19 | public static Mouse mouse; // Used for the mouse movement on system level (SharpDX)
20 | private IKeyboardMouseEvents globalHook; // Used for all the mouse and keyboard event capture
21 | private CH9329 MyCH9329; // The object which sends the data to us CH9329
22 | List videoSourceList = new List(); // Used to fill the combobox and obtain the ID to open the remote window
23 | public MainWindow()
24 | {
25 | // Initialize Window
26 | this.AppWindow.MoveAndResize(new Windows.Graphics.RectInt32(200, 100, 600, 350));
27 | this.AppWindow.SetIcon(@"Assets\control.ico");
28 | this.InitializeComponent();
29 | this.Closed += OnWindowClosed;
30 | PopulateComboBox();
31 | MainWindow.MessageControl = message;
32 |
33 | // SharpDX Mouse initialization to get raw mouse movement
34 | var directInput = new DirectInput();
35 | var firstMouseInstance = directInput.GetDevices(DeviceType.Mouse, DeviceEnumerationFlags.AllDevices).FirstOrDefault();
36 | mouse = new Mouse(directInput);
37 | mouse.Properties.AxisMode = DeviceAxisMode.Relative;
38 | mouse.Acquire();
39 |
40 | // Initialize CH3929 and Mouse hooks
41 | if (SetPort())
42 | {
43 | MyCH9329 = new CH9329(App.Flag.COMPort);
44 | globalHook = Hook.GlobalEvents();
45 | globalHook.KeyDown += GlobalHook_KeyDown;
46 | globalHook.KeyUp += GlobalHook_KeyUp;
47 | globalHook.MouseMove += GlobalHook_MouseMove;
48 | globalHook.MouseDown += GlobalHook_MouseDown;
49 | globalHook.MouseUp += GlobalHook_MouseUp;
50 | globalHook.MouseWheel += GlobalHook_MouseWheel;
51 | }
52 | }
53 | private async void PopulateComboBox()
54 | {
55 | string vSource = SettingsManager.GetSetting();
56 | Debug.WriteLine("From registry: "+vSource);
57 | var devices = await DeviceInformation.FindAllAsync(Windows.Devices.Enumeration.DeviceClass.VideoCapture);
58 | if (devices.Any())
59 | {
60 | Int32 i = 0;
61 | Int32 si = 0;
62 | foreach (var device in devices)
63 | {
64 | string name = device.Name.ToString();
65 | string id = device.Id;
66 | VideoSourceInfo videoSourceInfo = new VideoSourceInfo { Name = name, Id = id };
67 | videoSourceList.Add(videoSourceInfo);
68 | if (id == vSource) si = i;
69 | i++;
70 | }
71 | videoSource.ItemsSource = videoSourceList;
72 | videoSource.SelectedIndex = si;
73 | }
74 | }
75 | private void sourceSelected(object sender, SelectionChangedEventArgs e)
76 | {
77 | if (videoSource.SelectedItem != null)
78 | {
79 | VideoSourceInfo selectedVideoSource = (VideoSourceInfo)videoSource.SelectedItem;
80 | App.Flag.selectedSource = selectedVideoSource.Id;
81 | SettingsManager.SaveSetting(selectedVideoSource.Id);
82 | }
83 | }
84 | private void NoVideo_Click(object sender, RoutedEventArgs e)
85 | {
86 | if (App.Flag.COMPort != null)
87 | {
88 | MouseUtility.SetMouseCursorAside();
89 | mouse.GetCurrentState();
90 | App.Flag.Decoration = 0;
91 | App.Flag.isRemote = true;
92 | SetMessage("Remote Session Active", Colors.Blue);
93 | } else SetMessage("No KVM cable present", Colors.Red);
94 | }
95 | private void Video_Click(object sender, RoutedEventArgs e)
96 | {
97 | if (App.Flag.selectedSource != null)
98 | {
99 | if (App.Flag.COMPort!= null)
100 | {
101 | App.Flag.Decoration = 0;
102 | App.Flag.isRemote = true;
103 | App.Flag.isFullScreen = (sender as FrameworkElement)?.Tag?.ToString() == "True" || false; // Fullscreen button clicked?
104 |
105 | if (remoteInstance == null || remoteInstance.IsClosed) { remoteInstance = new Remote(); }
106 | remoteInstance.Activate();
107 | remoteInstance.SetFullScreen(App.Flag.isFullScreen);
108 |
109 | MouseUtility.SetMouseCursorAside();
110 | mouse.GetCurrentState();
111 | SetMessage("Remote Session Active", Colors.Blue);
112 | } else SetMessage("No KVM cable present", Colors.Red);
113 | } else SetMessage("No Videosource available", Colors.Red);
114 | }
115 | private void GlobalHook_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
116 | {
117 | if (!App.Flag.isRemote) { return; }
118 |
119 | if (e.KeyCode == System.Windows.Forms.Keys.RWin) { App.Flag.Decoration = (byte)(App.Flag.Decoration | (1 << 7)); }
120 | else if (e.KeyCode == System.Windows.Forms.Keys.RMenu) { App.Flag.Decoration = (byte)(App.Flag.Decoration | (1 << 6)); }
121 | else if (e.KeyCode == System.Windows.Forms.Keys.RShiftKey) { App.Flag.Decoration = (byte)(App.Flag.Decoration | (1 << 5)); }
122 | else if (e.KeyCode == System.Windows.Forms.Keys.RControlKey) { App.Flag.Decoration = (byte)(App.Flag.Decoration | (1 << 4)); }
123 | else if (e.KeyCode == System.Windows.Forms.Keys.LWin) { App.Flag.Decoration = (byte)(App.Flag.Decoration | (1 << 3)); }
124 | else if (e.KeyCode == System.Windows.Forms.Keys.LMenu) { App.Flag.Decoration = (byte)(App.Flag.Decoration | (1 << 2)); }
125 | else if (e.KeyCode == System.Windows.Forms.Keys.LShiftKey) { App.Flag.Decoration = (byte)(App.Flag.Decoration | (1 << 1)); ; }
126 | else if (e.KeyCode == System.Windows.Forms.Keys.LControlKey) { App.Flag.Decoration = (byte)(App.Flag.Decoration | (1 << 0)); }
127 | try
128 | {
129 | byte value = Flags.KeyMap[(byte)e.KeyValue];
130 | MyCH9329.charKeyType(App.Flag.Decoration, value);
131 | }
132 | catch (Exception ex) { if (ex != null) { } }
133 | e.Handled = true;
134 | }
135 | private static void GlobalHook_KeyUp(object sender, System.Windows.Forms.KeyEventArgs e)
136 | {
137 | if (!App.Flag.isRemote) { return; }
138 |
139 | if (e.KeyCode == System.Windows.Forms.Keys.RWin) { App.Flag.Decoration = (byte)(App.Flag.Decoration & ~(1 << 7)); }
140 | else if (e.KeyCode == System.Windows.Forms.Keys.RMenu) { App.Flag.Decoration = (byte)(App.Flag.Decoration & ~(1 << 6)); }
141 | else if (e.KeyCode == System.Windows.Forms.Keys.RShiftKey) { App.Flag.Decoration = (byte)(App.Flag.Decoration & ~(1 << 5)); }
142 | else if (e.KeyCode == System.Windows.Forms.Keys.RControlKey) { App.Flag.Decoration = (byte)(App.Flag.Decoration & ~(1 << 4)); }
143 | else if (e.KeyCode == System.Windows.Forms.Keys.LWin) { App.Flag.Decoration = (byte)(App.Flag.Decoration & ~(1 << 3)); }
144 | else if (e.KeyCode == System.Windows.Forms.Keys.LMenu) { App.Flag.Decoration = (byte)(App.Flag.Decoration & ~(1 << 2)); }
145 | else if (e.KeyCode == System.Windows.Forms.Keys.LShiftKey) { App.Flag.Decoration = (byte)(App.Flag.Decoration & ~(1 << 1)); }
146 | else if (e.KeyCode == System.Windows.Forms.Keys.LControlKey) { App.Flag.Decoration = (byte)(App.Flag.Decoration & ~(1 << 0)); }
147 |
148 | e.Handled = true;
149 | }
150 | private void GlobalHook_MouseWheel(object sender, System.Windows.Forms.MouseEventArgs e)
151 | {
152 | if (!App.Flag.isRemote) { return; }
153 |
154 | MyCH9329.mouseScroll(e.Delta < 0 ? -1 : 1);
155 | ((MouseEventExtArgs)e).Handled = true;
156 | }
157 | private void GlobalHook_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
158 | {
159 | if (!App.Flag.isRemote) { return; }
160 |
161 | MouseUtility.SetMouseCursorAside();
162 | if (!App.Flag.isMoving)
163 | {
164 | App.Flag.isMoving = true; // In CH9329 the flag is set to false again after the package is sent to remote. Prevents queue of movement on remote
165 | var mouseState = mouse.GetCurrentState();
166 | int X = mouseState.X; int Y = mouseState.Y;
167 | MyCH9329.mouseMoveRel(X, Y);
168 | }
169 | ((MouseEventExtArgs)e).Handled = true;
170 | }
171 | private void GlobalHook_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
172 | {
173 | if (!App.Flag.isRemote) { return; }
174 |
175 | if (e.Button == System.Windows.Forms.MouseButtons.Left) { MyCH9329.mouseButtonDown(CH9329.MouseButtonCode.LEFT); }
176 | else if (e.Button == System.Windows.Forms.MouseButtons.Right) { MyCH9329.mouseButtonDown(CH9329.MouseButtonCode.RIGHT); }
177 | else if (e.Button == System.Windows.Forms.MouseButtons.Middle)
178 | {
179 | if (App.Flag.isFullScreen) { remoteInstance.SetFullScreen(false); } else
180 | {
181 | App.Flag.isRemote = false;
182 | SetMessage("", Colors.Blue);
183 | }
184 | }
185 | ((MouseEventExtArgs)e).Handled = true;
186 | }
187 | private void GlobalHook_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
188 | {
189 | if (!App.Flag.isRemote) { return; }
190 |
191 | MyCH9329.mouseButtonUpAll();
192 | ((MouseEventExtArgs)e).Handled = true;
193 | }
194 | public void OnWindowClosed(object sender, WindowEventArgs e)
195 | {
196 | if (remoteInstance != null && !remoteInstance.IsClosed) { remoteInstance.Close(); }
197 | }
198 | public static void SetMessage(string line, object color)
199 | {
200 | if (MessageControl != null)
201 | {
202 | MessageControl.Text = line;
203 | MessageControl.Foreground = new SolidColorBrush((Windows.UI.Color)color);
204 | }
205 | }
206 | public bool SetPort()
207 | {
208 | Dictionary serialPorts = SerialPortUtility.GetSerialPorts();
209 | foreach (var port in serialPorts)
210 | {
211 | if (port.Value.Contains("VID_1A86&PID_7523"))
212 | {
213 | App.Flag.COMPort = port.Key;
214 | return true;
215 | }
216 | }
217 | SetMessage("No KVM cable present", Colors.Red);
218 | return false;
219 | }
220 | }
221 | }
222 |
--------------------------------------------------------------------------------
/Privacy Policy.txt:
--------------------------------------------------------------------------------
1 | Privacy Policy for Maintenance Tool Control3
2 | --------------------------------------------
3 |
4 | This Privacy Policy describes how the Maintenance Tool Control3 ("the Application") handles user data. The Application is a tool designed to configure your local devices via a direct USB connection.
5 |
6 | 1. Collection of Personal Information
7 | -------------------------------------
8 | The Application does not collect, store, or upload any personal information from users. It operates solely as a maintenance tool and does not require any user account creation or personal data input.
9 |
10 | 2. Data Storage
11 | ---------------
12 | No user data is stored by the Application. The tool operates locally on the user's device and does not retain any information beyond the duration of a session.
13 |
14 | 3. Data Sharing
15 | ---------------
16 | The Application does not collect or share any data with third parties. It operates independently between the user's local device and the device being configured, with no further external data transmission.
17 |
18 | 4. Data Usage
19 | -------------
20 | As no personal data is collected, stored, or shared by the Application, there are no specific data usage practices.
21 |
22 | 5. User Rights
23 | --------------
24 | Since no personal information is collected, there are no data-related rights for users to consider. The Application does not necessitate user registration or involve interaction with personal information.
25 |
26 | 6. Cookies and Tracking
27 | -----------------------
28 | The Application does not utilize cookies or tracking technologies. It operates as a standalone tool without any web or external interactions.
29 |
30 | 7. Updates to Privacy Policy
31 | ----------------------------
32 | Any updates to this Privacy Policy will be made available on this Application's GitHub page at https://github.com/sipper69/Control3.
33 |
--------------------------------------------------------------------------------
/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "Control3 (Package)": {
4 | "commandName": "MsixPackage"
5 | },
6 | "Control3 (Unpackaged)": {
7 | "commandName": "Project"
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Control3 - Laptop as KVM
2 |
3 | **Note:** This repository is not maintained, it is for inspirational purposes only.
4 | Most issues and discussion here are referring to the Microsoft App store version (https://apps.microsoft.com/detail/9pmw2phrdf46) which is a separate closed development.
5 | My apologies for the confusion.
6 |
7 | Demo: https://youtu.be/ZCbLUDXHe6E
8 |
9 | Test with VGA adapter: https://youtu.be/rz65hHhDxzw
10 |
11 | Use your laptop as Screen/Mouse/Keyboard to manage your external device like a RPi, (mini)PC, PlayStation, nVidia Shield, etc.
12 |
13 | I do not want that extra keyboard mouse and monitor when I do a quick configuration. There are tools, but I want direct BIOS access and no driver install on the remote device. There are KVM Console Crash Cart solutions, but you need a good budget. There are low budget products which can be used, but there was no tool to use them for this purpose.
14 |
15 | With Control3 you can use any MS2130 based video capture cable, they are blazing fast even over USB2.0, and supports Audio as well!
16 | For remote Mouse/Keyboard the CH9329 USB module in combination with a CH340 USB Serial module will do the job.
17 | Prefab cables of this combination are available (eBay: CH9329/CH340 KVM USB Cable, Ali: ch9329+ch340uart) or solder it yourself.
18 |
19 | I'm not a professional dev, but gave it a shot. Built a C# .NET 6 application to support the cables for remote control on my devices.
20 |
21 | Used the lastest Windows App SDK (WinUI 3) they support MediaPlayerElement with GUI support based on WinRT API's.
22 |
23 | Catch the keyboard/mouse events with the the MouseKeyHook library (https://github.com/gmamaladze/globalmousekeyhook)
24 |
25 | Catch the low-level mouse movements with the SharpDX.DirectInput library (https://github.com/sharpdx/SharpDX)
26 |
27 | Used the CH9329 Class from SmallCodeNote to send the mouse/keyboard data to the remote device (https://github.com/SmallCodeNote/CH9329-109KeyClass)
28 |
29 | CH9329 documentation translated to English.
30 | https://github.com/user-attachments/files/15741885/CH9329-CN.translated.PDF
31 |
32 | Some doc I created for AZERTY/QWERTZ Mapping:
33 | https://github.com/sipper69/Control3/blob/master/keycodes.xlsx
34 |
35 | All under MIT license, many thanks to these contributors, of course my contribution is open source as well.
36 |
37 |
38 | This is only a preview of what is possible, the tool is very usable for me already, but so many enhancements to think of.
39 | I do not deliver support, but like to discuss around it on Reddit (https://www.reddit.com/r/homelab/comments/1b6io6v/laptop_as_kvm/).
40 | This tool only support US keyboards btw. It runs on Windows 10/11 build 1809 and higher.
41 |
42 | I added an unsigned x64 binary, as packaged installer (SetupControl3.exe) if you want to play around directly. It will only recognize the product type of the cable I use (CH340), but you can amend the PID/VID in the code to support your cable.
43 | Don't forget to install a CH340 driver on your host PC if needed (check if the COM port is there under your devices).
44 | https://www.wch-ic.com/downloads/CH9340EVT_ZIP.html
45 |
46 | I hope it is of use to you!
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/Remote.xaml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Remote.xaml.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.UI.Xaml;
2 | using System;
3 | using Windows.Media.Capture;
4 | using System.Diagnostics;
5 | using Microsoft.UI.Windowing;
6 | using System.Linq;
7 | using Microsoft.UI.Dispatching;
8 | using Microsoft.UI;
9 |
10 | namespace Control3
11 | {
12 | public sealed partial class Remote : Window
13 | {
14 | private MediaCapture mediaCapture;
15 | public bool IsClosed { get; private set; }
16 | public Remote()
17 | {
18 | this.AppWindow.MoveAndResize(new Windows.Graphics.RectInt32(280, 40, 1280, 774));
19 | this.AppWindow.SetIcon(@"Assets\control.ico");
20 |
21 | this.InitializeComponent();
22 | SetFullScreen(App.Flag.isFullScreen);
23 | this.IsClosed = false;
24 |
25 | this.Closed += OnWindowClosed;
26 | this.Activated += OnWindowActivated;
27 |
28 | StartCaptureElement();
29 |
30 | MainWindow.mouse.GetCurrentState();
31 | }
32 | async private void StartCaptureElement()
33 | {
34 | mediaCapture = new MediaCapture();
35 | var settings = new MediaCaptureInitializationSettings { VideoDeviceId = App.Flag.selectedSource };
36 |
37 | await mediaCapture.InitializeAsync(settings);
38 |
39 | // var vidStream = mediaCapture.FrameSources.FirstOrDefault().Value;
40 | // Update: select Video Stream to prevent accidental usage of the audio stream
41 | var vidStream = mediaCapture.FrameSources
42 | .FirstOrDefault(source => source.Value.Info.MediaStreamType == MediaStreamType.VideoPreview ||
43 | source.Value.Info.MediaStreamType == MediaStreamType.VideoRecord).Value;
44 |
45 | captureElement.Source = Windows.Media.Core.MediaSource.CreateFromMediaFrameSource(vidStream);
46 |
47 | var desiredFormat = vidStream.SupportedFormats.FirstOrDefault(format => format.Subtype == "MJPG" && format.VideoFormat.Width == 1920 && format.FrameRate.Numerator == 30);
48 | if (desiredFormat != null)
49 | {
50 | await vidStream.SetFormatAsync(desiredFormat);
51 | Debug.WriteLine($"Format set: {desiredFormat.Subtype} {desiredFormat.FrameRate.Numerator} {desiredFormat.VideoFormat.Width}");
52 | }
53 | }
54 | public void OnWindowClosed(object sender, WindowEventArgs e)
55 | {
56 | DispatcherQueue.TryEnqueue(() =>
57 | {
58 | if (mediaCapture != null)
59 | {
60 | mediaCapture.Dispose();
61 | mediaCapture = null;
62 | }
63 | App.Flag.isRemote = false;
64 | this.IsClosed = true;
65 | MainWindow.SetMessage("", Colors.Blue);
66 | });
67 | }
68 | private void captureClicked(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
69 | {
70 | if (e.GetCurrentPoint(sender as UIElement).Properties.PointerUpdateKind == Microsoft.UI.Input.PointerUpdateKind.LeftButtonPressed)
71 | {
72 | MainWindow.mouse.GetCurrentState();
73 | App.Flag.Decoration = 0;
74 | App.Flag.isRemote = true;
75 | MainWindow.SetMessage("Remote Session Active", Colors.Blue);
76 | }
77 | }
78 | private void OnWindowActivated(object sender, Microsoft.UI.Xaml.WindowActivatedEventArgs e)
79 | {
80 | if (e.WindowActivationState == WindowActivationState.CodeActivated)
81 | {
82 | MainWindow.mouse.GetCurrentState();
83 | App.Flag.Decoration = 0;
84 | App.Flag.isRemote = true;
85 | MainWindow.SetMessage("Remote Session Active", Colors.Blue);
86 | }
87 | }
88 | public void SetFullScreen(bool fullScreen)
89 | {
90 | if (!fullScreen)
91 | {
92 | this.AppWindow.SetPresenter(AppWindowPresenterKind.Default);
93 | ImageBorder.BorderThickness = new Thickness(5);
94 | App.Flag.isFullScreen = false;
95 | }
96 | else
97 | {
98 | this.AppWindow.SetPresenter(AppWindowPresenterKind.FullScreen);
99 | ImageBorder.BorderThickness = new Thickness(0);
100 | App.Flag.isFullScreen = true;
101 | }
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/SetupControl3.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sipper69/Control3/b54ced6eb02490892561bf0a3d11767c79e0c335/SetupControl3.exe
--------------------------------------------------------------------------------
/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | PerMonitorV2
17 |
18 |
19 |
--------------------------------------------------------------------------------
/keycodes.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sipper69/Control3/b54ced6eb02490892561bf0a3d11767c79e0c335/keycodes.xlsx
--------------------------------------------------------------------------------