StephaneLenclud@125: using System; StephaneLenclud@126: using System.IO; StephaneLenclud@125: using System.Collections.Generic; StephaneLenclud@125: using System.Linq; StephaneLenclud@125: using System.Text; StephaneLenclud@125: using System.Threading.Tasks; StephaneLenclud@125: using System.Diagnostics; StephaneLenclud@125: using System.Runtime.InteropServices; StephaneLenclud@125: using System.Windows.Forms; StephaneLenclud@150: using Microsoft.Win32.SafeHandles; StephaneLenclud@155: using System.ComponentModel; StephaneLenclud@125: // StephaneLenclud@125: using Hid = SharpLib.Hid; StephaneLenclud@125: using SharpLib.Win32; StephaneLenclud@125: StephaneLenclud@125: namespace SharpDisplayManager StephaneLenclud@125: { StephaneLenclud@138: /// StephaneLenclud@138: /// Implement handling of HID input reports notably to be able to launch an application using the Green Start button from IR remotes. StephaneLenclud@138: /// StephaneLenclud@131: [System.ComponentModel.DesignerCategory("Code")] StephaneLenclud@226: public class FormMainHid : Form StephaneLenclud@131: { StephaneLenclud@131: [System.Runtime.InteropServices.DllImportAttribute("user32.dll", EntryPoint = "SwitchToThisWindow")] StephaneLenclud@131: public static extern void SwitchToThisWindow([System.Runtime.InteropServices.InAttribute()] System.IntPtr hwnd, [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.Bool)] bool fUnknown); StephaneLenclud@131: // StephaneLenclud@131: public delegate void OnHidEventDelegate(object aSender, Hid.Event aHidEvent); StephaneLenclud@126: StephaneLenclud@131: /// StephaneLenclud@131: /// Use notably to handle green start key from IR remote control StephaneLenclud@131: /// StephaneLenclud@131: private Hid.Handler iHidHandler; StephaneLenclud@126: StephaneLenclud@131: /// StephaneLenclud@131: /// Register HID devices so that we receive corresponding WM_INPUT messages. StephaneLenclud@131: /// StephaneLenclud@131: protected void RegisterHidDevices() StephaneLenclud@131: { StephaneLenclud@131: // Register the input device to receive the commands from the StephaneLenclud@131: // remote device. See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwmt/html/remote_control.asp StephaneLenclud@131: // for the vendor defined usage page. StephaneLenclud@128: StephaneLenclud@131: RAWINPUTDEVICE[] rid = new RAWINPUTDEVICE[5]; StephaneLenclud@128: StephaneLenclud@131: int i = 0; StephaneLenclud@131: rid[i].usUsagePage = (ushort)SharpLib.Hid.UsagePage.WindowsMediaCenterRemoteControl; StephaneLenclud@131: rid[i].usUsage = (ushort)SharpLib.Hid.UsageCollection.WindowsMediaCenter.WindowsMediaCenterRemoteControl; StephaneLenclud@131: rid[i].dwFlags = Const.RIDEV_INPUTSINK; StephaneLenclud@131: rid[i].hwndTarget = Handle; StephaneLenclud@128: StephaneLenclud@131: i++; StephaneLenclud@131: rid[i].usUsagePage = (ushort)SharpLib.Hid.UsagePage.Consumer; StephaneLenclud@131: rid[i].usUsage = (ushort)SharpLib.Hid.UsageCollection.Consumer.ConsumerControl; StephaneLenclud@131: rid[i].dwFlags = Const.RIDEV_INPUTSINK; StephaneLenclud@131: rid[i].hwndTarget = Handle; StephaneLenclud@126: StephaneLenclud@131: i++; StephaneLenclud@131: rid[i].usUsagePage = (ushort)SharpLib.Hid.UsagePage.Consumer; StephaneLenclud@131: rid[i].usUsage = (ushort)SharpLib.Hid.UsageCollection.Consumer.Selection; StephaneLenclud@131: rid[i].dwFlags = Const.RIDEV_INPUTSINK; StephaneLenclud@131: rid[i].hwndTarget = Handle; StephaneLenclud@125: StephaneLenclud@131: i++; StephaneLenclud@131: rid[i].usUsagePage = (ushort)SharpLib.Hid.UsagePage.GenericDesktopControls; StephaneLenclud@131: rid[i].usUsage = (ushort)SharpLib.Hid.UsageCollection.GenericDesktop.SystemControl; StephaneLenclud@131: rid[i].dwFlags = Const.RIDEV_INPUTSINK; StephaneLenclud@131: rid[i].hwndTarget = Handle; StephaneLenclud@125: StephaneLenclud@131: i++; StephaneLenclud@131: rid[i].usUsagePage = (ushort)SharpLib.Hid.UsagePage.GenericDesktopControls; StephaneLenclud@131: rid[i].usUsage = (ushort)SharpLib.Hid.UsageCollection.GenericDesktop.GamePad; StephaneLenclud@131: rid[i].dwFlags = Const.RIDEV_INPUTSINK; StephaneLenclud@131: rid[i].hwndTarget = Handle; StephaneLenclud@125: StephaneLenclud@131: //i++; StephaneLenclud@131: //rid[i].usUsagePage = (ushort)SharpLib.Hid.UsagePage.GenericDesktopControls; StephaneLenclud@131: //rid[i].usUsage = (ushort)SharpLib.Hid.UsageCollection.GenericDesktop.Keyboard; StephaneLenclud@131: //rid[i].dwFlags = Const.RIDEV_EXINPUTSINK; StephaneLenclud@131: //rid[i].hwndTarget = Handle; StephaneLenclud@125: StephaneLenclud@131: //i++; StephaneLenclud@131: //rid[i].usUsagePage = (ushort)Hid.UsagePage.GenericDesktopControls; StephaneLenclud@131: //rid[i].usUsage = (ushort)Hid.UsageCollection.GenericDesktop.Mouse; StephaneLenclud@131: //rid[i].dwFlags = Const.RIDEV_EXINPUTSINK; StephaneLenclud@131: //rid[i].hwndTarget = aHWND; StephaneLenclud@125: StephaneLenclud@125: StephaneLenclud@131: iHidHandler = new SharpLib.Hid.Handler(rid); StephaneLenclud@131: if (!iHidHandler.IsRegistered) StephaneLenclud@131: { StephaneLenclud@131: Debug.WriteLine("Failed to register raw input devices: " + Marshal.GetLastWin32Error().ToString()); StephaneLenclud@131: } StephaneLenclud@131: iHidHandler.OnHidEvent += HandleHidEventThreadSafe; StephaneLenclud@159: StephaneLenclud@131: } StephaneLenclud@125: StephaneLenclud@159: StephaneLenclud@159: StephaneLenclud@159: StephaneLenclud@131: /// StephaneLenclud@131: /// Here we receive HID events from our HID library. StephaneLenclud@131: /// StephaneLenclud@131: /// StephaneLenclud@131: /// StephaneLenclud@131: public void HandleHidEventThreadSafe(object aSender, SharpLib.Hid.Event aHidEvent) StephaneLenclud@131: { StephaneLenclud@131: if (aHidEvent.IsStray) StephaneLenclud@131: { StephaneLenclud@131: //Stray event just ignore it StephaneLenclud@131: return; StephaneLenclud@131: } StephaneLenclud@125: StephaneLenclud@131: if (this.InvokeRequired) StephaneLenclud@131: { StephaneLenclud@131: //Not in the proper thread, invoke ourselves StephaneLenclud@131: OnHidEventDelegate d = new OnHidEventDelegate(HandleHidEventThreadSafe); StephaneLenclud@131: this.Invoke(d, new object[] { aSender, aHidEvent }); StephaneLenclud@131: } StephaneLenclud@131: else StephaneLenclud@131: { StephaneLenclud@150: if (aHidEvent.Usages.Count == 0) StephaneLenclud@150: { StephaneLenclud@150: //No usage, nothing to do then StephaneLenclud@150: return; StephaneLenclud@150: } StephaneLenclud@150: StephaneLenclud@131: //We are in the proper thread StephaneLenclud@150: if (aHidEvent.UsagePage == (ushort) Hid.UsagePage.WindowsMediaCenterRemoteControl) StephaneLenclud@131: { StephaneLenclud@150: switch (aHidEvent.Usages[0]) StephaneLenclud@131: { StephaneLenclud@150: case (ushort)Hid.Usage.WindowsMediaCenterRemoteControl.GreenStart: StephaneLenclud@150: HandleGreenStart(); StephaneLenclud@150: break; StephaneLenclud@150: case (ushort)Hid.Usage.WindowsMediaCenterRemoteControl.Eject: StephaneLenclud@150: case (ushort)Hid.Usage.WindowsMediaCenterRemoteControl.Ext2: StephaneLenclud@150: HandleEject(); StephaneLenclud@150: break; StephaneLenclud@131: } StephaneLenclud@131: } StephaneLenclud@152: StephaneLenclud@152: //Keep this for debug when only ThinkPad keyboard is available StephaneLenclud@152: if (aHidEvent.UsagePage == (ushort)Hid.UsagePage.Consumer && aHidEvent.Usages[0] == (ushort)Hid.Usage.ConsumerControl.ThinkPadFullscreenMagnifier) StephaneLenclud@152: { StephaneLenclud@152: HandleEject(); StephaneLenclud@152: } StephaneLenclud@152: StephaneLenclud@131: } StephaneLenclud@131: } StephaneLenclud@125: StephaneLenclud@151: /// StephaneLenclud@151: /// StephaneLenclud@151: /// StephaneLenclud@155: /// StephaneLenclud@155: private void CheckLastError(string aPrefix) StephaneLenclud@155: { StephaneLenclud@155: string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message; StephaneLenclud@155: Debug.WriteLine(aPrefix + Marshal.GetLastWin32Error().ToString() + ": " + errorMessage); StephaneLenclud@155: } StephaneLenclud@155: StephaneLenclud@155: /// StephaneLenclud@155: /// StephaneLenclud@155: /// StephaneLenclud@151: /// StephaneLenclud@151: /// StephaneLenclud@151: private IntPtr MarshalToPointer(object data) StephaneLenclud@151: { StephaneLenclud@151: IntPtr buf = Marshal.AllocHGlobal( StephaneLenclud@151: Marshal.SizeOf(data)); StephaneLenclud@151: Marshal.StructureToPtr(data, StephaneLenclud@151: buf, false); StephaneLenclud@151: return buf; StephaneLenclud@151: } StephaneLenclud@151: StephaneLenclud@151: /// StephaneLenclud@151: /// StephaneLenclud@151: /// StephaneLenclud@151: /// StephaneLenclud@152: private SafeFileHandle OpenVolume(string aDriveName) StephaneLenclud@150: { StephaneLenclud@152: return Function.CreateFile("\\\\.\\" + aDriveName, StephaneLenclud@150: SharpLib.Win32.FileAccess.GENERIC_READ, StephaneLenclud@150: SharpLib.Win32.FileShare.FILE_SHARE_READ | SharpLib.Win32.FileShare.FILE_SHARE_WRITE, StephaneLenclud@150: IntPtr.Zero, StephaneLenclud@150: CreationDisposition.OPEN_EXISTING, StephaneLenclud@150: 0, StephaneLenclud@150: IntPtr.Zero); StephaneLenclud@150: } StephaneLenclud@150: StephaneLenclud@150: /// StephaneLenclud@150: /// StephaneLenclud@150: /// StephaneLenclud@151: /// StephaneLenclud@151: /// StephaneLenclud@151: private bool LockVolume(SafeFileHandle aVolume) StephaneLenclud@151: { StephaneLenclud@151: //Hope that's doing what I think it does StephaneLenclud@151: IntPtr dwBytesReturned=new IntPtr(); StephaneLenclud@151: //Should not be needed but I'm not sure how to pass NULL in there. StephaneLenclud@151: OVERLAPPED overlapped=new OVERLAPPED(); StephaneLenclud@151: StephaneLenclud@151: int tries = 0; StephaneLenclud@151: const int KMaxTries = 100; StephaneLenclud@151: const int KSleepTime = 10; StephaneLenclud@151: bool success = false; StephaneLenclud@151: StephaneLenclud@151: while (!success && tries < KMaxTries) StephaneLenclud@151: { StephaneLenclud@151: success = Function.DeviceIoControl(aVolume, Const.FSCTL_LOCK_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped); StephaneLenclud@151: System.Threading.Thread.Sleep(KSleepTime); StephaneLenclud@151: tries++; StephaneLenclud@151: } StephaneLenclud@151: StephaneLenclud@155: CheckLastError("Lock volume: "); StephaneLenclud@155: StephaneLenclud@151: return success; StephaneLenclud@151: } StephaneLenclud@151: StephaneLenclud@151: /// StephaneLenclud@151: /// StephaneLenclud@151: /// StephaneLenclud@151: /// StephaneLenclud@151: /// StephaneLenclud@151: private bool DismountVolume(SafeFileHandle aVolume) StephaneLenclud@151: { StephaneLenclud@151: //Hope that's doing what I think it does StephaneLenclud@151: IntPtr dwBytesReturned = new IntPtr(); StephaneLenclud@151: //Should not be needed but I'm not sure how to pass NULL in there. StephaneLenclud@151: OVERLAPPED overlapped=new OVERLAPPED(); StephaneLenclud@151: StephaneLenclud@155: bool res = Function.DeviceIoControl(aVolume, Const.FSCTL_DISMOUNT_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped); StephaneLenclud@155: CheckLastError("Dismount volume: "); StephaneLenclud@155: return res; StephaneLenclud@151: } StephaneLenclud@151: StephaneLenclud@151: StephaneLenclud@151: StephaneLenclud@151: /// StephaneLenclud@151: /// StephaneLenclud@151: /// StephaneLenclud@151: /// StephaneLenclud@151: /// StephaneLenclud@151: /// StephaneLenclud@151: private bool PreventRemovalOfVolume(SafeFileHandle aVolume, bool aPreventRemoval) StephaneLenclud@151: { StephaneLenclud@151: //Hope that's doing what I think it does StephaneLenclud@151: IntPtr dwBytesReturned = new IntPtr(); StephaneLenclud@151: //Should not be needed but I'm not sure how to pass NULL in there. StephaneLenclud@151: OVERLAPPED overlapped = new OVERLAPPED(); StephaneLenclud@151: // StephaneLenclud@151: PREVENT_MEDIA_REMOVAL preventMediaRemoval = new PREVENT_MEDIA_REMOVAL(); StephaneLenclud@151: preventMediaRemoval.PreventMediaRemoval = Convert.ToByte(aPreventRemoval); StephaneLenclud@151: IntPtr preventMediaRemovalParam = MarshalToPointer(preventMediaRemoval); StephaneLenclud@151: StephaneLenclud@151: bool result = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_MEDIA_REMOVAL, preventMediaRemovalParam, Convert.ToUInt32(Marshal.SizeOf(preventMediaRemoval)), IntPtr.Zero, 0, dwBytesReturned, ref overlapped); StephaneLenclud@155: CheckLastError("Media removal: "); StephaneLenclud@151: Marshal.FreeHGlobal(preventMediaRemovalParam); StephaneLenclud@151: StephaneLenclud@151: return result; StephaneLenclud@151: } StephaneLenclud@151: StephaneLenclud@151: /// StephaneLenclud@154: /// Eject optical drive media opening the tray if any. StephaneLenclud@151: /// StephaneLenclud@151: /// StephaneLenclud@151: /// StephaneLenclud@154: private bool MediaEject(SafeFileHandle aVolume) StephaneLenclud@151: { StephaneLenclud@151: //Hope that's doing what I think it does StephaneLenclud@151: IntPtr dwBytesReturned = new IntPtr(); StephaneLenclud@151: //Should not be needed but I'm not sure how to pass NULL in there. StephaneLenclud@151: OVERLAPPED overlapped=new OVERLAPPED(); StephaneLenclud@151: StephaneLenclud@155: bool res = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_EJECT_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped); StephaneLenclud@155: CheckLastError("Media eject: "); StephaneLenclud@155: return res; StephaneLenclud@151: } StephaneLenclud@151: StephaneLenclud@152: /// StephaneLenclud@154: /// Close an optical drive tray. StephaneLenclud@152: /// StephaneLenclud@152: /// StephaneLenclud@152: /// StephaneLenclud@154: private bool MediaLoad(SafeFileHandle aVolume) StephaneLenclud@152: { StephaneLenclud@152: //Hope that's doing what I think it does StephaneLenclud@152: IntPtr dwBytesReturned = new IntPtr(); StephaneLenclud@152: //Should not be needed but I'm not sure how to pass NULL in there. StephaneLenclud@152: OVERLAPPED overlapped=new OVERLAPPED(); StephaneLenclud@152: StephaneLenclud@155: bool res = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_LOAD_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped); StephaneLenclud@155: CheckLastError("Media load: "); StephaneLenclud@155: return res; StephaneLenclud@152: } StephaneLenclud@152: StephaneLenclud@154: /// StephaneLenclud@154: /// StephaneLenclud@154: /// StephaneLenclud@154: /// StephaneLenclud@154: /// StephaneLenclud@154: private bool StorageCheckVerify(SafeFileHandle aVolume) StephaneLenclud@154: { StephaneLenclud@154: //Hope that's doing what I think it does StephaneLenclud@154: IntPtr dwBytesReturned = new IntPtr(); StephaneLenclud@154: //Should not be needed but I'm not sure how to pass NULL in there. StephaneLenclud@154: OVERLAPPED overlapped = new OVERLAPPED(); StephaneLenclud@154: StephaneLenclud@154: bool res = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_CHECK_VERIFY2, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped); StephaneLenclud@154: StephaneLenclud@155: CheckLastError("Check verify: "); StephaneLenclud@154: StephaneLenclud@154: return res; StephaneLenclud@154: } StephaneLenclud@151: StephaneLenclud@151: StephaneLenclud@151: StephaneLenclud@151: /// StephaneLenclud@151: /// Perform media ejection. StephaneLenclud@151: /// StephaneLenclud@150: private void HandleEject() StephaneLenclud@150: { StephaneLenclud@226: string drive = ((FormMain)this).OpticalDriveToEject(); StephaneLenclud@153: if (drive.Length!=2) StephaneLenclud@153: { StephaneLenclud@153: //Not a proper drive spec. StephaneLenclud@153: //Probably 'None' selected. StephaneLenclud@153: return; StephaneLenclud@153: } StephaneLenclud@153: StephaneLenclud@153: SafeFileHandle handle = OpenVolume(drive); StephaneLenclud@151: if (handle.IsInvalid) StephaneLenclud@151: { StephaneLenclud@155: CheckLastError("ERROR: Failed to open volume: "); StephaneLenclud@151: return; StephaneLenclud@151: } StephaneLenclud@151: StephaneLenclud@151: if (LockVolume(handle) && DismountVolume(handle)) StephaneLenclud@151: { StephaneLenclud@154: Debug.WriteLine("Volume was dismounted."); StephaneLenclud@151: StephaneLenclud@152: if (PreventRemovalOfVolume(handle,false)) StephaneLenclud@151: { StephaneLenclud@156: //StorageCheckVerify(handle); StephaneLenclud@154: StephaneLenclud@156: DateTime before; StephaneLenclud@156: before = DateTime.Now; StephaneLenclud@156: bool ejectSuccess = MediaEject(handle); StephaneLenclud@156: double ms = (DateTime.Now - before).TotalMilliseconds; StephaneLenclud@156: StephaneLenclud@156: //We assume that if it take more than a certain time to for eject to execute it means we actually ejected. StephaneLenclud@156: //If our eject completes too rapidly we assume the tray is already open and we will try to close it. StephaneLenclud@156: if (ejectSuccess && ms > 100) StephaneLenclud@152: { StephaneLenclud@154: Debug.WriteLine("Media was ejected"); StephaneLenclud@152: } StephaneLenclud@154: else if (MediaLoad(handle)) StephaneLenclud@154: { StephaneLenclud@154: Debug.WriteLine("Media was loaded"); StephaneLenclud@154: } StephaneLenclud@151: } StephaneLenclud@151: } StephaneLenclud@154: else StephaneLenclud@154: { StephaneLenclud@154: Debug.WriteLine("Volume lock or dismount failed."); StephaneLenclud@154: } StephaneLenclud@154: StephaneLenclud@154: //This is needed to make sure we can open the volume next time around StephaneLenclud@154: handle.Dispose(); StephaneLenclud@150: } StephaneLenclud@150: StephaneLenclud@150: /// StephaneLenclud@150: /// StephaneLenclud@150: /// StephaneLenclud@150: private void HandleGreenStart() StephaneLenclud@150: { StephaneLenclud@150: //First check if the process we want to launch already exists StephaneLenclud@150: string procName = Path.GetFileNameWithoutExtension(Properties.Settings.Default.StartFileName); StephaneLenclud@150: Process[] existingProcesses = Process.GetProcessesByName(procName); StephaneLenclud@150: if (existingProcesses == null || existingProcesses.Length == 0) StephaneLenclud@150: { StephaneLenclud@150: // Process do not exists just try to launch it StephaneLenclud@150: ProcessStartInfo start = new ProcessStartInfo(); StephaneLenclud@150: // Enter in the command line arguments, everything you would enter after the executable name itself StephaneLenclud@150: //start.Arguments = arguments; StephaneLenclud@150: // Enter the executable to run, including the complete path StephaneLenclud@150: start.FileName = Properties.Settings.Default.StartFileName; StephaneLenclud@150: start.WindowStyle = ProcessWindowStyle.Normal; StephaneLenclud@150: start.CreateNoWindow = true; StephaneLenclud@150: start.UseShellExecute = true; StephaneLenclud@150: // Run the external process & wait for it to finish StephaneLenclud@150: Process proc = Process.Start(start); StephaneLenclud@150: StephaneLenclud@150: //SL: We could have used that too StephaneLenclud@150: //Shell32.Shell shell = new Shell32.Shell(); StephaneLenclud@150: //shell.ShellExecute(Properties.Settings.Default.StartFileName); StephaneLenclud@150: } StephaneLenclud@150: else StephaneLenclud@150: { StephaneLenclud@150: //This won't work properly until we have a manifest that enables uiAccess. StephaneLenclud@150: //However uiAccess just won't work with ClickOnce so we will have to use a different deployment system. StephaneLenclud@150: SwitchToThisWindow(existingProcesses[0].MainWindowHandle, true); StephaneLenclud@150: } StephaneLenclud@150: } StephaneLenclud@167: StephaneLenclud@167: StephaneLenclud@131: /// StephaneLenclud@131: /// We need to handle WM_INPUT. StephaneLenclud@131: /// StephaneLenclud@131: /// StephaneLenclud@131: protected override void WndProc(ref Message message) StephaneLenclud@131: { StephaneLenclud@131: switch (message.Msg) StephaneLenclud@131: { StephaneLenclud@131: case Const.WM_INPUT: StephaneLenclud@131: //Returning zero means we processed that message. StephaneLenclud@131: message.Result = new IntPtr(0); StephaneLenclud@131: iHidHandler.ProcessInput(ref message); StephaneLenclud@131: break; StephaneLenclud@131: } StephaneLenclud@159: StephaneLenclud@167: //Pass this on to base class. StephaneLenclud@131: base.WndProc(ref message); StephaneLenclud@131: } StephaneLenclud@131: } StephaneLenclud@125: }