Server/FormMain.Hid.cs
author StephaneLenclud
Thu, 18 Aug 2016 17:43:03 +0200
changeset 240 5c4f1e2bf29a
parent 237 1a1c2ae3a29c
child 241 3b5a94f31400
permissions -rw-r--r--
Adding empty class for eject optical drive action.
     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 using System.ComponentModel;
    12 //
    13 using Hid = SharpLib.Hid;
    14 using SharpLib.Win32;
    15 
    16 namespace SharpDisplayManager
    17 {
    18     /// <summary>
    19     /// Implement handling of HID input reports notably to be able to launch an application using the Green Start button from IR remotes.
    20     /// </summary>
    21     [System.ComponentModel.DesignerCategory("Code")]
    22     public class FormMainHid : Form
    23     {
    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);
    26         //
    27         public delegate void OnHidEventDelegate(object aSender, Hid.Event aHidEvent);
    28 
    29         /// <summary>
    30         /// Use notably to handle green start key from IR remote control
    31         /// </summary>
    32         private Hid.Handler iHidHandler;
    33 
    34         /// <summary>
    35         /// Register HID devices so that we receive corresponding WM_INPUT messages.
    36         /// </summary>
    37         protected void RegisterHidDevices()
    38         {
    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.
    42 
    43             RAWINPUTDEVICE[] rid = new RAWINPUTDEVICE[5];
    44 
    45             int i = 0;
    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;
    50 
    51             i++;
    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;
    56 
    57             i++;
    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;
    62 
    63             i++;
    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;
    68 
    69             i++;
    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;
    74 
    75             //i++;
    76             //rid[i].usUsagePage = (ushort)SharpLib.Hid.UsagePage.GenericDesktopControls;
    77             //rid[i].usUsage = (ushort)SharpLib.Hid.UsageCollection.GenericDesktop.Keyboard;
    78             //rid[i].dwFlags = Const.RIDEV_EXINPUTSINK;
    79             //rid[i].hwndTarget = Handle;
    80 
    81             //i++;
    82             //rid[i].usUsagePage = (ushort)Hid.UsagePage.GenericDesktopControls;
    83             //rid[i].usUsage = (ushort)Hid.UsageCollection.GenericDesktop.Mouse;
    84             //rid[i].dwFlags = Const.RIDEV_EXINPUTSINK;
    85             //rid[i].hwndTarget = aHWND;
    86 
    87 
    88             iHidHandler = new SharpLib.Hid.Handler(rid);
    89             if (!iHidHandler.IsRegistered)
    90             {
    91                 Debug.WriteLine("Failed to register raw input devices: " + Marshal.GetLastWin32Error().ToString());
    92             }
    93             iHidHandler.OnHidEvent += HandleHidEventThreadSafe;
    94 
    95         }
    96 
    97 
    98 
    99 
   100         /// <summary>
   101         /// Here we receive HID events from our HID library.
   102         /// </summary>
   103         /// <param name="aSender"></param>
   104         /// <param name="aHidEvent"></param>
   105         public void HandleHidEventThreadSafe(object aSender, SharpLib.Hid.Event aHidEvent)
   106         {
   107             if (aHidEvent.IsStray)
   108             {
   109                 //Stray event just ignore it
   110                 return;
   111             }
   112 
   113             if (this.InvokeRequired)
   114             {
   115                 //Not in the proper thread, invoke ourselves
   116                 OnHidEventDelegate d = new OnHidEventDelegate(HandleHidEventThreadSafe);
   117                 this.Invoke(d, new object[] { aSender, aHidEvent });
   118             }
   119             else
   120             {
   121                 if (aHidEvent.Usages.Count == 0)
   122                 {
   123                     //No usage, nothing to do then
   124                     return;
   125                 }
   126 
   127                 //We are in the proper thread
   128                 if (aHidEvent.UsagePage == (ushort) Hid.UsagePage.WindowsMediaCenterRemoteControl)
   129                 {
   130                     //Trigger events as needed
   131                     EventHidWindowsMediaCenter e = new EventHidWindowsMediaCenter { Usage = (Hid.Usage.WindowsMediaCenterRemoteControl)aHidEvent.Usages[0] };
   132                     Properties.Settings.Default.EarManager.TriggerEvent(e);
   133 
   134                     //Old legacy hard coded stuff
   135                     //TODO: remove it
   136                     switch (aHidEvent.Usages[0])
   137                     {
   138                         case (ushort)Hid.Usage.WindowsMediaCenterRemoteControl.GreenStart:
   139                             HandleGreenStart();
   140                             break;
   141                         case (ushort)Hid.Usage.WindowsMediaCenterRemoteControl.Eject:
   142                         case (ushort)Hid.Usage.WindowsMediaCenterRemoteControl.Ext2:
   143                             HandleEject();
   144                             break;
   145                     }
   146                 }
   147                 else if (aHidEvent.UsagePage == (ushort)Hid.UsagePage.Consumer)
   148                 {
   149                     EventHidConsumerControl e = new EventHidConsumerControl { Usage = (Hid.Usage.ConsumerControl)aHidEvent.Usages[0] };
   150                     Properties.Settings.Default.EarManager.TriggerEvent(e);
   151 
   152                     //Keep this for debug when only ThinkPad keyboard is available
   153                     //if (aHidEvent.Usages[0] == (ushort)Hid.Usage.ConsumerControl.ThinkPadFullscreenMagnifier)
   154                     //{
   155                     //    HandleEject();
   156                     //}
   157                 }
   158 
   159             }
   160         }
   161 
   162         /// <summary>
   163         /// 
   164         /// </summary>
   165         /// <param name="aPrefix"></param>
   166         private void CheckLastError(string aPrefix)
   167         {
   168             string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;
   169             Debug.WriteLine(aPrefix + Marshal.GetLastWin32Error().ToString() + ": " + errorMessage);
   170         }
   171 
   172         /// <summary>
   173         /// 
   174         /// </summary>
   175         /// <param name="data"></param>
   176         /// <returns></returns>
   177         private IntPtr MarshalToPointer(object data)
   178         {
   179             IntPtr buf = Marshal.AllocHGlobal(
   180                 Marshal.SizeOf(data));
   181             Marshal.StructureToPtr(data,
   182                 buf, false);
   183             return buf;
   184         }
   185 
   186         /// <summary>
   187         /// 
   188         /// </summary>
   189         /// <returns></returns>
   190         private SafeFileHandle OpenVolume(string aDriveName)
   191         {
   192             return Function.CreateFile("\\\\.\\" + aDriveName,
   193                                SharpLib.Win32.FileAccess.GENERIC_READ,
   194                                SharpLib.Win32.FileShare.FILE_SHARE_READ | SharpLib.Win32.FileShare.FILE_SHARE_WRITE,
   195                                IntPtr.Zero,
   196                                CreationDisposition.OPEN_EXISTING,
   197                                0,
   198                                IntPtr.Zero);
   199         }
   200 
   201         /// <summary>
   202         /// 
   203         /// </summary>
   204         /// <param name="aVolume"></param>
   205         /// <returns></returns>
   206         private bool LockVolume(SafeFileHandle aVolume)
   207         {
   208             //Hope that's doing what I think it does
   209             IntPtr dwBytesReturned=new IntPtr();
   210             //Should not be needed but I'm not sure how to pass NULL in there.
   211             OVERLAPPED overlapped=new OVERLAPPED();
   212 
   213             int tries = 0;
   214             const int KMaxTries = 100;
   215             const int KSleepTime = 10;
   216             bool success = false;
   217 
   218             while (!success && tries < KMaxTries)
   219             {
   220                 success = Function.DeviceIoControl(aVolume, Const.FSCTL_LOCK_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
   221                 System.Threading.Thread.Sleep(KSleepTime);
   222                 tries++;
   223             }
   224 
   225             CheckLastError("Lock volume: ");
   226 
   227             return success;
   228         }
   229 
   230         /// <summary>
   231         /// 
   232         /// </summary>
   233         /// <param name="aVolume"></param>
   234         /// <returns></returns>
   235         private bool DismountVolume(SafeFileHandle aVolume)
   236         {
   237             //Hope that's doing what I think it does
   238             IntPtr dwBytesReturned = new IntPtr();
   239             //Should not be needed but I'm not sure how to pass NULL in there.
   240             OVERLAPPED overlapped=new OVERLAPPED();
   241 
   242             bool res = Function.DeviceIoControl(aVolume, Const.FSCTL_DISMOUNT_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
   243             CheckLastError("Dismount volume: ");
   244             return res;
   245         }
   246 
   247 
   248 
   249         /// <summary>
   250         /// 
   251         /// </summary>
   252         /// <param name="aVolume"></param>
   253         /// <param name="aPreventRemoval"></param>
   254         /// <returns></returns>
   255         private bool PreventRemovalOfVolume(SafeFileHandle aVolume, bool aPreventRemoval)
   256         {
   257             //Hope that's doing what I think it does
   258             IntPtr dwBytesReturned = new IntPtr();
   259             //Should not be needed but I'm not sure how to pass NULL in there.
   260             OVERLAPPED overlapped = new OVERLAPPED();
   261             //
   262             PREVENT_MEDIA_REMOVAL preventMediaRemoval = new PREVENT_MEDIA_REMOVAL();
   263             preventMediaRemoval.PreventMediaRemoval = Convert.ToByte(aPreventRemoval);
   264             IntPtr preventMediaRemovalParam = MarshalToPointer(preventMediaRemoval);
   265 
   266             bool result = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_MEDIA_REMOVAL, preventMediaRemovalParam, Convert.ToUInt32(Marshal.SizeOf(preventMediaRemoval)), IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
   267             CheckLastError("Media removal: ");
   268             Marshal.FreeHGlobal(preventMediaRemovalParam);
   269 
   270             return result;
   271         }
   272 
   273         /// <summary>
   274         /// Eject optical drive media opening the tray if any.
   275         /// </summary>
   276         /// <param name="aVolume"></param>
   277         /// <returns></returns>
   278         private bool MediaEject(SafeFileHandle aVolume)
   279         {
   280             //Hope that's doing what I think it does
   281             IntPtr dwBytesReturned = new IntPtr();
   282             //Should not be needed but I'm not sure how to pass NULL in there.
   283             OVERLAPPED overlapped=new OVERLAPPED();
   284 
   285             bool res = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_EJECT_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
   286             CheckLastError("Media eject: ");
   287             return res;
   288         }
   289 
   290         /// <summary>
   291         /// Close an optical drive tray.
   292         /// </summary>
   293         /// <param name="aVolume"></param>
   294         /// <returns></returns>
   295         private bool MediaLoad(SafeFileHandle aVolume)
   296         {
   297             //Hope that's doing what I think it does
   298             IntPtr dwBytesReturned = new IntPtr();
   299             //Should not be needed but I'm not sure how to pass NULL in there.
   300             OVERLAPPED overlapped=new OVERLAPPED();
   301 
   302             bool res = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_LOAD_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
   303             CheckLastError("Media load: ");
   304             return res;
   305         }
   306 
   307         /// <summary>
   308         /// 
   309         /// </summary>
   310         /// <param name="aVolume"></param>
   311         /// <returns></returns>
   312         private bool StorageCheckVerify(SafeFileHandle aVolume)
   313         {
   314             //Hope that's doing what I think it does
   315             IntPtr dwBytesReturned = new IntPtr();
   316             //Should not be needed but I'm not sure how to pass NULL in there.
   317             OVERLAPPED overlapped = new OVERLAPPED();
   318 
   319             bool res = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_CHECK_VERIFY2, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
   320 
   321             CheckLastError("Check verify: ");
   322 
   323             return res;
   324         }        
   325         
   326 
   327 
   328         /// <summary>
   329         /// Perform media ejection.
   330         /// </summary>
   331         private void HandleEject()
   332         {
   333             string drive = ((FormMain)this).OpticalDriveToEject();
   334             if (drive.Length!=2)
   335             {
   336                 //Not a proper drive spec.
   337                 //Probably 'None' selected.
   338                 return;
   339             }
   340 
   341             SafeFileHandle handle = OpenVolume(drive);
   342             if (handle.IsInvalid)
   343             {
   344                 CheckLastError("ERROR: Failed to open volume: ");
   345                 return;
   346             }
   347 
   348             if (LockVolume(handle) && DismountVolume(handle))
   349             {
   350                 Debug.WriteLine("Volume was dismounted.");
   351 
   352                 if (PreventRemovalOfVolume(handle,false))
   353                 {
   354                     //StorageCheckVerify(handle);
   355 
   356                     DateTime before;
   357                     before = DateTime.Now;
   358                     bool ejectSuccess = MediaEject(handle);
   359                     double ms = (DateTime.Now - before).TotalMilliseconds;
   360 
   361                     //We assume that if it take more than a certain time to for eject to execute it means we actually ejected.
   362                     //If our eject completes too rapidly we assume the tray is already open and we will try to close it. 
   363                     if (ejectSuccess && ms > 100)
   364                     {
   365                         Debug.WriteLine("Media was ejected");
   366                     }
   367                     else if (MediaLoad(handle))
   368                     {
   369                         Debug.WriteLine("Media was loaded");
   370                     }                    
   371                 }
   372             }
   373             else
   374             {
   375                 Debug.WriteLine("Volume lock or dismount failed.");
   376             }
   377 
   378             //This is needed to make sure we can open the volume next time around
   379             handle.Dispose();
   380         }
   381 
   382         /// <summary>
   383         /// 
   384         /// </summary>
   385         private void HandleGreenStart()
   386         {
   387             //First check if the process we want to launch already exists
   388             string procName = Path.GetFileNameWithoutExtension(Properties.Settings.Default.StartFileName);
   389             Process[] existingProcesses = Process.GetProcessesByName(procName);
   390             if (existingProcesses == null || existingProcesses.Length == 0)
   391             {
   392                 // Process do not exists just try to launch it
   393                 ProcessStartInfo start = new ProcessStartInfo();
   394                 // Enter in the command line arguments, everything you would enter after the executable name itself
   395                 //start.Arguments = arguments; 
   396                 // Enter the executable to run, including the complete path
   397                 start.FileName = Properties.Settings.Default.StartFileName;
   398                 start.WindowStyle = ProcessWindowStyle.Normal;
   399                 start.CreateNoWindow = true;
   400                 start.UseShellExecute = true;
   401                 // Run the external process & wait for it to finish
   402                 Process proc = Process.Start(start);
   403 
   404                 //SL: We could have used that too
   405                 //Shell32.Shell shell = new Shell32.Shell();
   406                 //shell.ShellExecute(Properties.Settings.Default.StartFileName);
   407             }
   408             else
   409             {
   410                 //This won't work properly until we have a manifest that enables uiAccess.
   411                 //However uiAccess just won't work with ClickOnce so we will have to use a different deployment system.
   412                 SwitchToThisWindow(existingProcesses[0].MainWindowHandle, true);
   413             }            
   414         }
   415 
   416 
   417         /// <summary>
   418         /// We need to handle WM_INPUT.
   419         /// </summary>
   420         /// <param name="message"></param>
   421         protected override void WndProc(ref Message message)
   422         {
   423             switch (message.Msg)
   424             {
   425                 case Const.WM_INPUT:
   426                     //Returning zero means we processed that message.
   427                     message.Result = new IntPtr(0);
   428                     iHidHandler.ProcessInput(ref message);
   429                     break;
   430             }
   431 
   432             //Pass this on to base class.
   433             base.WndProc(ref message);
   434         }
   435     }
   436 }