Server/MainForm.Hid.cs
author StephaneLenclud
Wed, 02 Sep 2015 16:02:24 +0200
changeset 153 95f253aaf588
parent 152 03a1757a38df
child 154 5ecbb2f57a16
permissions -rw-r--r--
Persisting selection of optical drive to eject.
     1 using System;
     2 using System.IO;
     3 using System.Collections.Generic;
     4 using System.Linq;
     5 using System.Text;
     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 //
    12 using Hid = SharpLib.Hid;
    13 using SharpLib.Win32;
    14 
    15 namespace SharpDisplayManager
    16 {
    17     /// <summary>
    18     /// Implement handling of HID input reports notably to be able to launch an application using the Green Start button from IR remotes.
    19     /// </summary>
    20     [System.ComponentModel.DesignerCategory("Code")]
    21     public class MainFormHid : Form
    22     {
    23         [System.Runtime.InteropServices.DllImportAttribute("user32.dll", EntryPoint = "SwitchToThisWindow")]
    24         public static extern void SwitchToThisWindow([System.Runtime.InteropServices.InAttribute()] System.IntPtr hwnd, [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.Bool)] bool fUnknown);
    25         //
    26         public delegate void OnHidEventDelegate(object aSender, Hid.Event aHidEvent);
    27 
    28         /// <summary>
    29         /// Use notably to handle green start key from IR remote control
    30         /// </summary>
    31         private Hid.Handler iHidHandler;
    32 
    33         /// <summary>
    34         /// Register HID devices so that we receive corresponding WM_INPUT messages.
    35         /// </summary>
    36         protected void RegisterHidDevices()
    37         {
    38             // Register the input device to receive the commands from the
    39             // remote device. See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwmt/html/remote_control.asp
    40             // for the vendor defined usage page.
    41 
    42             RAWINPUTDEVICE[] rid = new RAWINPUTDEVICE[5];
    43 
    44             int i = 0;
    45             rid[i].usUsagePage = (ushort)SharpLib.Hid.UsagePage.WindowsMediaCenterRemoteControl;
    46             rid[i].usUsage = (ushort)SharpLib.Hid.UsageCollection.WindowsMediaCenter.WindowsMediaCenterRemoteControl;
    47             rid[i].dwFlags = Const.RIDEV_INPUTSINK;
    48             rid[i].hwndTarget = Handle;
    49 
    50             i++;
    51             rid[i].usUsagePage = (ushort)SharpLib.Hid.UsagePage.Consumer;
    52             rid[i].usUsage = (ushort)SharpLib.Hid.UsageCollection.Consumer.ConsumerControl;
    53             rid[i].dwFlags = Const.RIDEV_INPUTSINK;
    54             rid[i].hwndTarget = Handle;
    55 
    56             i++;
    57             rid[i].usUsagePage = (ushort)SharpLib.Hid.UsagePage.Consumer;
    58             rid[i].usUsage = (ushort)SharpLib.Hid.UsageCollection.Consumer.Selection;
    59             rid[i].dwFlags = Const.RIDEV_INPUTSINK;
    60             rid[i].hwndTarget = Handle;
    61 
    62             i++;
    63             rid[i].usUsagePage = (ushort)SharpLib.Hid.UsagePage.GenericDesktopControls;
    64             rid[i].usUsage = (ushort)SharpLib.Hid.UsageCollection.GenericDesktop.SystemControl;
    65             rid[i].dwFlags = Const.RIDEV_INPUTSINK;
    66             rid[i].hwndTarget = Handle;
    67 
    68             i++;
    69             rid[i].usUsagePage = (ushort)SharpLib.Hid.UsagePage.GenericDesktopControls;
    70             rid[i].usUsage = (ushort)SharpLib.Hid.UsageCollection.GenericDesktop.GamePad;
    71             rid[i].dwFlags = Const.RIDEV_INPUTSINK;
    72             rid[i].hwndTarget = Handle;
    73 
    74             //i++;
    75             //rid[i].usUsagePage = (ushort)SharpLib.Hid.UsagePage.GenericDesktopControls;
    76             //rid[i].usUsage = (ushort)SharpLib.Hid.UsageCollection.GenericDesktop.Keyboard;
    77             //rid[i].dwFlags = Const.RIDEV_EXINPUTSINK;
    78             //rid[i].hwndTarget = Handle;
    79 
    80             //i++;
    81             //rid[i].usUsagePage = (ushort)Hid.UsagePage.GenericDesktopControls;
    82             //rid[i].usUsage = (ushort)Hid.UsageCollection.GenericDesktop.Mouse;
    83             //rid[i].dwFlags = Const.RIDEV_EXINPUTSINK;
    84             //rid[i].hwndTarget = aHWND;
    85 
    86 
    87             iHidHandler = new SharpLib.Hid.Handler(rid);
    88             if (!iHidHandler.IsRegistered)
    89             {
    90                 Debug.WriteLine("Failed to register raw input devices: " + Marshal.GetLastWin32Error().ToString());
    91             }
    92             iHidHandler.OnHidEvent += HandleHidEventThreadSafe;
    93         }
    94 
    95         /// <summary>
    96         /// Here we receive HID events from our HID library.
    97         /// </summary>
    98         /// <param name="aSender"></param>
    99         /// <param name="aHidEvent"></param>
   100         public void HandleHidEventThreadSafe(object aSender, SharpLib.Hid.Event aHidEvent)
   101         {
   102             if (aHidEvent.IsStray)
   103             {
   104                 //Stray event just ignore it
   105                 return;
   106             }
   107 
   108             if (this.InvokeRequired)
   109             {
   110                 //Not in the proper thread, invoke ourselves
   111                 OnHidEventDelegate d = new OnHidEventDelegate(HandleHidEventThreadSafe);
   112                 this.Invoke(d, new object[] { aSender, aHidEvent });
   113             }
   114             else
   115             {
   116                 if (aHidEvent.Usages.Count == 0)
   117                 {
   118                     //No usage, nothing to do then
   119                     return;
   120                 }
   121 
   122                 //We are in the proper thread
   123                 if (aHidEvent.UsagePage == (ushort) Hid.UsagePage.WindowsMediaCenterRemoteControl)
   124                 {
   125                     switch (aHidEvent.Usages[0])
   126                     {
   127                         case (ushort)Hid.Usage.WindowsMediaCenterRemoteControl.GreenStart:
   128                             HandleGreenStart();
   129                             break;
   130                         case (ushort)Hid.Usage.WindowsMediaCenterRemoteControl.Eject:
   131                         case (ushort)Hid.Usage.WindowsMediaCenterRemoteControl.Ext2:
   132                             HandleEject();
   133                             break;
   134                     }
   135                 }
   136 
   137                 //Keep this for debug when only ThinkPad keyboard is available
   138                 if (aHidEvent.UsagePage == (ushort)Hid.UsagePage.Consumer && aHidEvent.Usages[0] == (ushort)Hid.Usage.ConsumerControl.ThinkPadFullscreenMagnifier)
   139                 {
   140                     HandleEject();
   141                 }
   142 
   143             }
   144         }
   145 
   146         /// <summary>
   147         /// 
   148         /// </summary>
   149         /// <param name="data"></param>
   150         /// <returns></returns>
   151         private IntPtr MarshalToPointer(object data)
   152         {
   153             IntPtr buf = Marshal.AllocHGlobal(
   154                 Marshal.SizeOf(data));
   155             Marshal.StructureToPtr(data,
   156                 buf, false);
   157             return buf;
   158         }
   159 
   160         /// <summary>
   161         /// 
   162         /// </summary>
   163         /// <returns></returns>
   164         private SafeFileHandle OpenVolume(string aDriveName)
   165         {
   166             return Function.CreateFile("\\\\.\\" + aDriveName,
   167                                SharpLib.Win32.FileAccess.GENERIC_READ,
   168                                SharpLib.Win32.FileShare.FILE_SHARE_READ | SharpLib.Win32.FileShare.FILE_SHARE_WRITE,
   169                                IntPtr.Zero,
   170                                CreationDisposition.OPEN_EXISTING,
   171                                0,
   172                                IntPtr.Zero);
   173         }
   174 
   175         /// <summary>
   176         /// 
   177         /// </summary>
   178         /// <param name="aVolume"></param>
   179         /// <returns></returns>
   180         private bool LockVolume(SafeFileHandle aVolume)
   181         {
   182             //Hope that's doing what I think it does
   183             IntPtr dwBytesReturned=new IntPtr();
   184             //Should not be needed but I'm not sure how to pass NULL in there.
   185             OVERLAPPED overlapped=new OVERLAPPED();
   186 
   187             int tries = 0;
   188             const int KMaxTries = 100;
   189             const int KSleepTime = 10;
   190             bool success = false;
   191 
   192             while (!success && tries < KMaxTries)
   193             {
   194                 success = Function.DeviceIoControl(aVolume, Const.FSCTL_LOCK_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
   195                 System.Threading.Thread.Sleep(KSleepTime);
   196                 tries++;
   197             }
   198 
   199             return success;
   200         }
   201 
   202         /// <summary>
   203         /// 
   204         /// </summary>
   205         /// <param name="aVolume"></param>
   206         /// <returns></returns>
   207         private bool DismountVolume(SafeFileHandle aVolume)
   208         {
   209             //Hope that's doing what I think it does
   210             IntPtr dwBytesReturned = new IntPtr();
   211             //Should not be needed but I'm not sure how to pass NULL in there.
   212             OVERLAPPED overlapped=new OVERLAPPED();
   213 
   214             return Function.DeviceIoControl(aVolume, Const.FSCTL_DISMOUNT_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
   215         }
   216 
   217 
   218 
   219         /// <summary>
   220         /// 
   221         /// </summary>
   222         /// <param name="aVolume"></param>
   223         /// <param name="aPreventRemoval"></param>
   224         /// <returns></returns>
   225         private bool PreventRemovalOfVolume(SafeFileHandle aVolume, bool aPreventRemoval)
   226         {
   227             //Hope that's doing what I think it does
   228             IntPtr dwBytesReturned = new IntPtr();
   229             //Should not be needed but I'm not sure how to pass NULL in there.
   230             OVERLAPPED overlapped = new OVERLAPPED();
   231             //
   232             PREVENT_MEDIA_REMOVAL preventMediaRemoval = new PREVENT_MEDIA_REMOVAL();
   233             preventMediaRemoval.PreventMediaRemoval = Convert.ToByte(aPreventRemoval);
   234             IntPtr preventMediaRemovalParam = MarshalToPointer(preventMediaRemoval);
   235 
   236             bool result = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_MEDIA_REMOVAL, preventMediaRemovalParam, Convert.ToUInt32(Marshal.SizeOf(preventMediaRemoval)), IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
   237 
   238             Marshal.FreeHGlobal(preventMediaRemovalParam);
   239 
   240             return result;
   241         }
   242 
   243         /// <summary>
   244         /// 
   245         /// </summary>
   246         /// <param name="aVolume"></param>
   247         /// <returns></returns>
   248         private bool AutoEjectVolume(SafeFileHandle aVolume)
   249         {
   250             //Hope that's doing what I think it does
   251             IntPtr dwBytesReturned = new IntPtr();
   252             //Should not be needed but I'm not sure how to pass NULL in there.
   253             OVERLAPPED overlapped=new OVERLAPPED();
   254 
   255             return Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_EJECT_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
   256         }
   257 
   258         /// <summary>
   259         /// Not working.
   260         /// </summary>
   261         /// <param name="aVolume"></param>
   262         /// <returns></returns>
   263         private bool CloseTray(SafeFileHandle aVolume)
   264         {
   265             //Hope that's doing what I think it does
   266             IntPtr dwBytesReturned = new IntPtr();
   267             //Should not be needed but I'm not sure how to pass NULL in there.
   268             OVERLAPPED overlapped=new OVERLAPPED();
   269 
   270             return Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_LOAD_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
   271         }
   272 
   273         
   274         
   275 
   276 
   277         /// <summary>
   278         /// Perform media ejection.
   279         /// </summary>
   280         private void HandleEject()
   281         {
   282             string drive = ((MainForm)this).OpticalDriveToEject();
   283             if (drive.Length!=2)
   284             {
   285                 //Not a proper drive spec.
   286                 //Probably 'None' selected.
   287                 return;
   288             }
   289 
   290             SafeFileHandle handle = OpenVolume(drive);
   291             if (handle.IsInvalid)
   292             {
   293                 return;
   294             }
   295 
   296             if (LockVolume(handle) && DismountVolume(handle))
   297             {
   298                 Debug.Write("Volume was dismounted.");
   299 
   300                 if (PreventRemovalOfVolume(handle,false))
   301                 {
   302                     if (AutoEjectVolume(handle))
   303                     {
   304                         Debug.Write("Media was ejected");
   305                     }
   306                     //else if (CloseTray(handle))
   307                     //{
   308                     //    Debug.Write("Media was loaded");
   309                     //}                    
   310                 }
   311             }
   312         }
   313 
   314         /// <summary>
   315         /// 
   316         /// </summary>
   317         private void HandleGreenStart()
   318         {
   319             //First check if the process we want to launch already exists
   320             string procName = Path.GetFileNameWithoutExtension(Properties.Settings.Default.StartFileName);
   321             Process[] existingProcesses = Process.GetProcessesByName(procName);
   322             if (existingProcesses == null || existingProcesses.Length == 0)
   323             {
   324                 // Process do not exists just try to launch it
   325                 ProcessStartInfo start = new ProcessStartInfo();
   326                 // Enter in the command line arguments, everything you would enter after the executable name itself
   327                 //start.Arguments = arguments; 
   328                 // Enter the executable to run, including the complete path
   329                 start.FileName = Properties.Settings.Default.StartFileName;
   330                 start.WindowStyle = ProcessWindowStyle.Normal;
   331                 start.CreateNoWindow = true;
   332                 start.UseShellExecute = true;
   333                 // Run the external process & wait for it to finish
   334                 Process proc = Process.Start(start);
   335 
   336                 //SL: We could have used that too
   337                 //Shell32.Shell shell = new Shell32.Shell();
   338                 //shell.ShellExecute(Properties.Settings.Default.StartFileName);
   339             }
   340             else
   341             {
   342                 //This won't work properly until we have a manifest that enables uiAccess.
   343                 //However uiAccess just won't work with ClickOnce so we will have to use a different deployment system.
   344                 SwitchToThisWindow(existingProcesses[0].MainWindowHandle, true);
   345             }            
   346         }
   347         /// <summary>
   348         /// We need to handle WM_INPUT.
   349         /// </summary>
   350         /// <param name="message"></param>
   351         protected override void WndProc(ref Message message)
   352         {
   353             switch (message.Msg)
   354             {
   355                 case Const.WM_INPUT:
   356                     //Returning zero means we processed that message.
   357                     message.Result = new IntPtr(0);
   358                     iHidHandler.ProcessInput(ref message);
   359                     break;
   360             }
   361             //Is that needed? Check the docs.
   362             base.WndProc(ref message);
   363         }
   364     }
   365 }