Adding non functional generic EAR HID event.
3 using System.Collections.Generic;
6 using System.Threading.Tasks;
7 using System.Diagnostics;
8 using System.Runtime.InteropServices;
9 using System.Windows.Forms;
10 using Microsoft.Win32.SafeHandles;
11 using System.ComponentModel;
13 using Hid = SharpLib.Hid;
16 namespace SharpDisplayManager
19 /// Implement handling of HID input reports notably to be able to launch an application using the Green Start button from IR remotes.
21 [System.ComponentModel.DesignerCategory("Code")]
22 public class FormMainHid : Form
24 [System.Runtime.InteropServices.DllImportAttribute("user32.dll", EntryPoint = "SwitchToThisWindow")]
25 public static extern void SwitchToThisWindow([System.Runtime.InteropServices.InAttribute()] System.IntPtr hwnd, [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.Bool)] bool fUnknown);
27 public delegate void OnHidEventDelegate(object aSender, Hid.Event aHidEvent);
30 /// Use notably to handle green start key from IR remote control
32 private Hid.Handler iHidHandler;
35 /// Register HID devices so that we receive corresponding WM_INPUT messages.
37 protected void RegisterHidDevices()
39 // Register the input device to receive the commands from the
40 // remote device. See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwmt/html/remote_control.asp
41 // for the vendor defined usage page.
43 RAWINPUTDEVICE[] rid = new RAWINPUTDEVICE[6];
46 rid[i].usUsagePage = (ushort)SharpLib.Hid.UsagePage.WindowsMediaCenterRemoteControl;
47 rid[i].usUsage = (ushort)SharpLib.Hid.UsageCollection.WindowsMediaCenter.WindowsMediaCenterRemoteControl;
48 rid[i].dwFlags = RawInputDeviceFlags.RIDEV_INPUTSINK;
49 rid[i].hwndTarget = Handle;
52 rid[i].usUsagePage = (ushort)SharpLib.Hid.UsagePage.Consumer;
53 rid[i].usUsage = (ushort)SharpLib.Hid.UsageCollection.Consumer.ConsumerControl;
54 rid[i].dwFlags = RawInputDeviceFlags.RIDEV_INPUTSINK;
55 rid[i].hwndTarget = Handle;
58 rid[i].usUsagePage = (ushort)SharpLib.Hid.UsagePage.Consumer;
59 rid[i].usUsage = (ushort)SharpLib.Hid.UsageCollection.Consumer.Selection;
60 rid[i].dwFlags = RawInputDeviceFlags.RIDEV_INPUTSINK;
61 rid[i].hwndTarget = Handle;
64 rid[i].usUsagePage = (ushort)SharpLib.Hid.UsagePage.GenericDesktopControls;
65 rid[i].usUsage = (ushort)SharpLib.Hid.UsageCollection.GenericDesktop.SystemControl;
66 rid[i].dwFlags = RawInputDeviceFlags.RIDEV_INPUTSINK;
67 rid[i].hwndTarget = Handle;
70 rid[i].usUsagePage = (ushort)SharpLib.Hid.UsagePage.GenericDesktopControls;
71 rid[i].usUsage = (ushort)SharpLib.Hid.UsageCollection.GenericDesktop.GamePad;
72 rid[i].dwFlags = RawInputDeviceFlags.RIDEV_INPUTSINK;
73 rid[i].hwndTarget = Handle;
76 rid[i].usUsagePage = (ushort)SharpLib.Hid.UsagePage.GenericDesktopControls;
77 rid[i].usUsage = (ushort)SharpLib.Hid.UsageCollection.GenericDesktop.Keyboard;
78 rid[i].dwFlags = RawInputDeviceFlags.RIDEV_INPUTSINK;
79 rid[i].hwndTarget = Handle;
82 //rid[i].usUsagePage = (ushort)SharpLib.Hid.UsagePage.GenericDesktopControls;
83 //rid[i].usUsage = (ushort)SharpLib.Hid.UsageCollection.GenericDesktop.Keyboard;
84 //rid[i].dwFlags = Const.RIDEV_EXINPUTSINK;
85 //rid[i].hwndTarget = Handle;
88 //rid[i].usUsagePage = (ushort)Hid.UsagePage.GenericDesktopControls;
89 //rid[i].usUsage = (ushort)Hid.UsageCollection.GenericDesktop.Mouse;
90 //rid[i].dwFlags = Const.RIDEV_EXINPUTSINK;
91 //rid[i].hwndTarget = aHWND;
94 iHidHandler = new SharpLib.Hid.Handler(rid);
95 if (!iHidHandler.IsRegistered)
97 Debug.WriteLine("Failed to register raw input devices: " + Marshal.GetLastWin32Error().ToString());
99 iHidHandler.OnHidEvent += HandleHidEventThreadSafe;
107 /// Here we receive HID events from our HID library.
109 /// <param name="aSender"></param>
110 /// <param name="aHidEvent"></param>
111 public void HandleHidEventThreadSafe(object aSender, SharpLib.Hid.Event aHidEvent)
113 if (aHidEvent.IsStray || !aHidEvent.IsValid)
115 //Stray event just ignore it
119 if (this.InvokeRequired)
121 //Not in the proper thread, invoke ourselves
122 OnHidEventDelegate d = new OnHidEventDelegate(HandleHidEventThreadSafe);
123 this.Invoke(d, new object[] { aSender, aHidEvent });
127 if (aHidEvent.IsGeneric)
129 if (aHidEvent.Usages.Count == 0)
131 //No usage, nothing to do then
135 //We are in the proper thread
136 if (aHidEvent.UsagePage == (ushort) Hid.UsagePage.WindowsMediaCenterRemoteControl)
138 //Trigger events as needed
139 EventHidWindowsMediaCenter e = new EventHidWindowsMediaCenter
141 Usage = (Hid.Usage.WindowsMediaCenterRemoteControl) aHidEvent.Usages[0]
143 Properties.Settings.Default.EarManager.TriggerEvent(e);
145 //Old legacy hard coded stuff
147 switch (aHidEvent.Usages[0])
149 case (ushort) Hid.Usage.WindowsMediaCenterRemoteControl.GreenStart:
152 case (ushort) Hid.Usage.WindowsMediaCenterRemoteControl.Eject:
153 case (ushort) Hid.Usage.WindowsMediaCenterRemoteControl.Ext2:
158 else if (aHidEvent.UsagePage == (ushort) Hid.UsagePage.Consumer)
160 //Trigger matching events if any
161 EventHidConsumerControl e = new EventHidConsumerControl
163 Usage = (Hid.Usage.ConsumerControl) aHidEvent.Usages[0]
165 Properties.Settings.Default.EarManager.TriggerEvent(e);
167 //Keep this for debug when only ThinkPad keyboard is available
168 //if (aHidEvent.Usages[0] == (ushort)Hid.Usage.ConsumerControl.ThinkPadFullscreenMagnifier)
174 else if (aHidEvent.IsKeyboard)
176 //Trigger matching events if any
177 EventHidKeyboard e = new EventHidKeyboard
179 Key = aHidEvent.VirtualKey,
180 IsKeyUp = aHidEvent.IsButtonUp,
181 HasModifierAlt = aHidEvent.HasModifierAlt,
182 HasModifierControl = aHidEvent.HasModifierControl,
183 HasModifierShift = aHidEvent.HasModifierShift,
184 HasModifierWindows = aHidEvent.HasModifierWindows,
186 Properties.Settings.Default.EarManager.TriggerEvent(e);
195 /// <param name="aPrefix"></param>
196 private void CheckLastError(string aPrefix)
198 string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;
199 Debug.WriteLine(aPrefix + Marshal.GetLastWin32Error().ToString() + ": " + errorMessage);
205 /// <param name="data"></param>
206 /// <returns></returns>
207 private IntPtr MarshalToPointer(object data)
209 IntPtr buf = Marshal.AllocHGlobal(
210 Marshal.SizeOf(data));
211 Marshal.StructureToPtr(data,
219 /// <returns></returns>
220 private SafeFileHandle OpenVolume(string aDriveName)
222 return Function.CreateFile("\\\\.\\" + aDriveName,
223 SharpLib.Win32.FileAccess.GENERIC_READ,
224 SharpLib.Win32.FileShare.FILE_SHARE_READ | SharpLib.Win32.FileShare.FILE_SHARE_WRITE,
226 CreationDisposition.OPEN_EXISTING,
234 /// <param name="aVolume"></param>
235 /// <returns></returns>
236 private bool LockVolume(SafeFileHandle aVolume)
238 //Hope that's doing what I think it does
239 IntPtr dwBytesReturned=new IntPtr();
240 //Should not be needed but I'm not sure how to pass NULL in there.
241 OVERLAPPED overlapped=new OVERLAPPED();
244 const int KMaxTries = 100;
245 const int KSleepTime = 10;
246 bool success = false;
248 while (!success && tries < KMaxTries)
250 success = Function.DeviceIoControl(aVolume, Const.FSCTL_LOCK_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
251 System.Threading.Thread.Sleep(KSleepTime);
255 CheckLastError("Lock volume: ");
263 /// <param name="aVolume"></param>
264 /// <returns></returns>
265 private bool DismountVolume(SafeFileHandle aVolume)
267 //Hope that's doing what I think it does
268 IntPtr dwBytesReturned = new IntPtr();
269 //Should not be needed but I'm not sure how to pass NULL in there.
270 OVERLAPPED overlapped=new OVERLAPPED();
272 bool res = Function.DeviceIoControl(aVolume, Const.FSCTL_DISMOUNT_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
273 CheckLastError("Dismount volume: ");
282 /// <param name="aVolume"></param>
283 /// <param name="aPreventRemoval"></param>
284 /// <returns></returns>
285 private bool PreventRemovalOfVolume(SafeFileHandle aVolume, bool aPreventRemoval)
287 //Hope that's doing what I think it does
288 IntPtr dwBytesReturned = new IntPtr();
289 //Should not be needed but I'm not sure how to pass NULL in there.
290 OVERLAPPED overlapped = new OVERLAPPED();
292 PREVENT_MEDIA_REMOVAL preventMediaRemoval = new PREVENT_MEDIA_REMOVAL();
293 preventMediaRemoval.PreventMediaRemoval = Convert.ToByte(aPreventRemoval);
294 IntPtr preventMediaRemovalParam = MarshalToPointer(preventMediaRemoval);
296 bool result = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_MEDIA_REMOVAL, preventMediaRemovalParam, Convert.ToUInt32(Marshal.SizeOf(preventMediaRemoval)), IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
297 CheckLastError("Media removal: ");
298 Marshal.FreeHGlobal(preventMediaRemovalParam);
304 /// Eject optical drive media opening the tray if any.
306 /// <param name="aVolume"></param>
307 /// <returns></returns>
308 private bool MediaEject(SafeFileHandle aVolume)
310 //Hope that's doing what I think it does
311 IntPtr dwBytesReturned = new IntPtr();
312 //Should not be needed but I'm not sure how to pass NULL in there.
313 OVERLAPPED overlapped=new OVERLAPPED();
315 bool res = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_EJECT_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
316 CheckLastError("Media eject: ");
321 /// Close an optical drive tray.
323 /// <param name="aVolume"></param>
324 /// <returns></returns>
325 private bool MediaLoad(SafeFileHandle aVolume)
327 //Hope that's doing what I think it does
328 IntPtr dwBytesReturned = new IntPtr();
329 //Should not be needed but I'm not sure how to pass NULL in there.
330 OVERLAPPED overlapped=new OVERLAPPED();
332 bool res = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_LOAD_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
333 CheckLastError("Media load: ");
340 /// <param name="aVolume"></param>
341 /// <returns></returns>
342 private bool StorageCheckVerify(SafeFileHandle aVolume)
344 //Hope that's doing what I think it does
345 IntPtr dwBytesReturned = new IntPtr();
346 //Should not be needed but I'm not sure how to pass NULL in there.
347 OVERLAPPED overlapped = new OVERLAPPED();
349 bool res = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_CHECK_VERIFY2, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
351 CheckLastError("Check verify: ");
359 /// Perform media ejection.
361 private void HandleEject()
363 string drive = ((FormMain)this).OpticalDriveToEject();
366 //Not a proper drive spec.
367 //Probably 'None' selected.
371 SafeFileHandle handle = OpenVolume(drive);
372 if (handle.IsInvalid)
374 CheckLastError("ERROR: Failed to open volume: ");
378 if (LockVolume(handle) && DismountVolume(handle))
380 Debug.WriteLine("Volume was dismounted.");
382 if (PreventRemovalOfVolume(handle,false))
384 //StorageCheckVerify(handle);
387 before = DateTime.Now;
388 bool ejectSuccess = MediaEject(handle);
389 double ms = (DateTime.Now - before).TotalMilliseconds;
391 //We assume that if it take more than a certain time to for eject to execute it means we actually ejected.
392 //If our eject completes too rapidly we assume the tray is already open and we will try to close it.
393 if (ejectSuccess && ms > 100)
395 Debug.WriteLine("Media was ejected");
397 else if (MediaLoad(handle))
399 Debug.WriteLine("Media was loaded");
405 Debug.WriteLine("Volume lock or dismount failed.");
408 //This is needed to make sure we can open the volume next time around
415 private void HandleGreenStart()
417 //First check if the process we want to launch already exists
418 string procName = Path.GetFileNameWithoutExtension(Properties.Settings.Default.StartFileName);
419 Process[] existingProcesses = Process.GetProcessesByName(procName);
420 if (existingProcesses == null || existingProcesses.Length == 0)
422 // Process do not exists just try to launch it
423 ProcessStartInfo start = new ProcessStartInfo();
424 // Enter in the command line arguments, everything you would enter after the executable name itself
425 //start.Arguments = arguments;
426 // Enter the executable to run, including the complete path
427 start.FileName = Properties.Settings.Default.StartFileName;
428 start.WindowStyle = ProcessWindowStyle.Normal;
429 start.CreateNoWindow = true;
430 start.UseShellExecute = true;
431 // Run the external process & wait for it to finish
432 Process proc = Process.Start(start);
434 //SL: We could have used that too
435 //Shell32.Shell shell = new Shell32.Shell();
436 //shell.ShellExecute(Properties.Settings.Default.StartFileName);
440 //This won't work properly until we have a manifest that enables uiAccess.
441 //However uiAccess just won't work with ClickOnce so we will have to use a different deployment system.
442 SwitchToThisWindow(existingProcesses[0].MainWindowHandle, true);
448 /// We need to handle WM_INPUT.
450 /// <param name="message"></param>
451 protected override void WndProc(ref Message message)
456 //Returning zero means we processed that message.
457 message.Result = new IntPtr(0);
458 iHidHandler.ProcessInput(ref message);
462 //Pass this on to base class.
463 base.WndProc(ref message);