Server/MainForm.Hid.cs
author StephaneLenclud
Sat, 26 Sep 2015 11:56:49 +0200
changeset 166 22b327842add
parent 160 de942d321cfb
child 167 d2295c186ce1
permissions -rw-r--r--
MiniDisplay NuGet upgrade to v1.1.8
     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 MainFormHid : 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         ///
    35         private PowerManager.SettingNotifier iPowerSettingNotifier;
    36 
    37         ///
    38         private Cec.Client iCecClient;
    39 
    40         /// <summary>
    41         /// Register HID devices so that we receive corresponding WM_INPUT messages.
    42         /// </summary>
    43         protected void RegisterHidDevices()
    44         {
    45             // Register the input device to receive the commands from the
    46             // remote device. See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwmt/html/remote_control.asp
    47             // for the vendor defined usage page.
    48 
    49             RAWINPUTDEVICE[] rid = new RAWINPUTDEVICE[5];
    50 
    51             int i = 0;
    52             rid[i].usUsagePage = (ushort)SharpLib.Hid.UsagePage.WindowsMediaCenterRemoteControl;
    53             rid[i].usUsage = (ushort)SharpLib.Hid.UsageCollection.WindowsMediaCenter.WindowsMediaCenterRemoteControl;
    54             rid[i].dwFlags = Const.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.ConsumerControl;
    60             rid[i].dwFlags = Const.RIDEV_INPUTSINK;
    61             rid[i].hwndTarget = Handle;
    62 
    63             i++;
    64             rid[i].usUsagePage = (ushort)SharpLib.Hid.UsagePage.Consumer;
    65             rid[i].usUsage = (ushort)SharpLib.Hid.UsageCollection.Consumer.Selection;
    66             rid[i].dwFlags = Const.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.SystemControl;
    72             rid[i].dwFlags = Const.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.GamePad;
    78             rid[i].dwFlags = Const.RIDEV_INPUTSINK;
    79             rid[i].hwndTarget = Handle;
    80 
    81             //i++;
    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;
    86 
    87             //i++;
    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;
    92 
    93 
    94             iHidHandler = new SharpLib.Hid.Handler(rid);
    95             if (!iHidHandler.IsRegistered)
    96             {
    97                 Debug.WriteLine("Failed to register raw input devices: " + Marshal.GetLastWin32Error().ToString());
    98             }
    99             iHidHandler.OnHidEvent += HandleHidEventThreadSafe;
   100 
   101             //TODO: Move this some place else
   102             iPowerSettingNotifier = new PowerManager.SettingNotifier(Handle);
   103             iPowerSettingNotifier.OnMonitorPowerOn += MonitorPowerOn;
   104             iPowerSettingNotifier.OnMonitorPowerOff += MonitorPowerOff;
   105 
   106             //CEC
   107             iCecClient = new Cec.Client();
   108             if (!iCecClient.Connect(1000))
   109             {
   110                 Debug.WriteLine("WARNING: No CEC connection!");
   111             }
   112         }
   113 
   114         void MonitorPowerOn()
   115         {
   116             Debug.WriteLine("ON");
   117             iCecClient.PowerOnDevices(CecSharp.CecLogicalAddress.Tv);
   118         }
   119 
   120         void MonitorPowerOff()
   121         {
   122             Debug.WriteLine("OFF");
   123             iCecClient.StandbyDevices(CecSharp.CecLogicalAddress.Tv);
   124         }
   125 
   126 
   127         /// <summary>
   128         /// Here we receive HID events from our HID library.
   129         /// </summary>
   130         /// <param name="aSender"></param>
   131         /// <param name="aHidEvent"></param>
   132         public void HandleHidEventThreadSafe(object aSender, SharpLib.Hid.Event aHidEvent)
   133         {
   134             if (aHidEvent.IsStray)
   135             {
   136                 //Stray event just ignore it
   137                 return;
   138             }
   139 
   140             if (this.InvokeRequired)
   141             {
   142                 //Not in the proper thread, invoke ourselves
   143                 OnHidEventDelegate d = new OnHidEventDelegate(HandleHidEventThreadSafe);
   144                 this.Invoke(d, new object[] { aSender, aHidEvent });
   145             }
   146             else
   147             {
   148                 if (aHidEvent.Usages.Count == 0)
   149                 {
   150                     //No usage, nothing to do then
   151                     return;
   152                 }
   153 
   154                 //We are in the proper thread
   155                 if (aHidEvent.UsagePage == (ushort) Hid.UsagePage.WindowsMediaCenterRemoteControl)
   156                 {
   157                     switch (aHidEvent.Usages[0])
   158                     {
   159                         case (ushort)Hid.Usage.WindowsMediaCenterRemoteControl.GreenStart:
   160                             HandleGreenStart();
   161                             break;
   162                         case (ushort)Hid.Usage.WindowsMediaCenterRemoteControl.Eject:
   163                         case (ushort)Hid.Usage.WindowsMediaCenterRemoteControl.Ext2:
   164                             HandleEject();
   165                             break;
   166                     }
   167                 }
   168 
   169                 //Keep this for debug when only ThinkPad keyboard is available
   170                 if (aHidEvent.UsagePage == (ushort)Hid.UsagePage.Consumer && aHidEvent.Usages[0] == (ushort)Hid.Usage.ConsumerControl.ThinkPadFullscreenMagnifier)
   171                 {
   172                     HandleEject();
   173                 }
   174 
   175             }
   176         }
   177 
   178         /// <summary>
   179         /// 
   180         /// </summary>
   181         /// <param name="aPrefix"></param>
   182         private void CheckLastError(string aPrefix)
   183         {
   184             string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;
   185             Debug.WriteLine(aPrefix + Marshal.GetLastWin32Error().ToString() + ": " + errorMessage);
   186         }
   187 
   188         /// <summary>
   189         /// 
   190         /// </summary>
   191         /// <param name="data"></param>
   192         /// <returns></returns>
   193         private IntPtr MarshalToPointer(object data)
   194         {
   195             IntPtr buf = Marshal.AllocHGlobal(
   196                 Marshal.SizeOf(data));
   197             Marshal.StructureToPtr(data,
   198                 buf, false);
   199             return buf;
   200         }
   201 
   202         /// <summary>
   203         /// 
   204         /// </summary>
   205         /// <returns></returns>
   206         private SafeFileHandle OpenVolume(string aDriveName)
   207         {
   208             return Function.CreateFile("\\\\.\\" + aDriveName,
   209                                SharpLib.Win32.FileAccess.GENERIC_READ,
   210                                SharpLib.Win32.FileShare.FILE_SHARE_READ | SharpLib.Win32.FileShare.FILE_SHARE_WRITE,
   211                                IntPtr.Zero,
   212                                CreationDisposition.OPEN_EXISTING,
   213                                0,
   214                                IntPtr.Zero);
   215         }
   216 
   217         /// <summary>
   218         /// 
   219         /// </summary>
   220         /// <param name="aVolume"></param>
   221         /// <returns></returns>
   222         private bool LockVolume(SafeFileHandle aVolume)
   223         {
   224             //Hope that's doing what I think it does
   225             IntPtr dwBytesReturned=new IntPtr();
   226             //Should not be needed but I'm not sure how to pass NULL in there.
   227             OVERLAPPED overlapped=new OVERLAPPED();
   228 
   229             int tries = 0;
   230             const int KMaxTries = 100;
   231             const int KSleepTime = 10;
   232             bool success = false;
   233 
   234             while (!success && tries < KMaxTries)
   235             {
   236                 success = Function.DeviceIoControl(aVolume, Const.FSCTL_LOCK_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
   237                 System.Threading.Thread.Sleep(KSleepTime);
   238                 tries++;
   239             }
   240 
   241             CheckLastError("Lock volume: ");
   242 
   243             return success;
   244         }
   245 
   246         /// <summary>
   247         /// 
   248         /// </summary>
   249         /// <param name="aVolume"></param>
   250         /// <returns></returns>
   251         private bool DismountVolume(SafeFileHandle aVolume)
   252         {
   253             //Hope that's doing what I think it does
   254             IntPtr dwBytesReturned = new IntPtr();
   255             //Should not be needed but I'm not sure how to pass NULL in there.
   256             OVERLAPPED overlapped=new OVERLAPPED();
   257 
   258             bool res = Function.DeviceIoControl(aVolume, Const.FSCTL_DISMOUNT_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
   259             CheckLastError("Dismount volume: ");
   260             return res;
   261         }
   262 
   263 
   264 
   265         /// <summary>
   266         /// 
   267         /// </summary>
   268         /// <param name="aVolume"></param>
   269         /// <param name="aPreventRemoval"></param>
   270         /// <returns></returns>
   271         private bool PreventRemovalOfVolume(SafeFileHandle aVolume, bool aPreventRemoval)
   272         {
   273             //Hope that's doing what I think it does
   274             IntPtr dwBytesReturned = new IntPtr();
   275             //Should not be needed but I'm not sure how to pass NULL in there.
   276             OVERLAPPED overlapped = new OVERLAPPED();
   277             //
   278             PREVENT_MEDIA_REMOVAL preventMediaRemoval = new PREVENT_MEDIA_REMOVAL();
   279             preventMediaRemoval.PreventMediaRemoval = Convert.ToByte(aPreventRemoval);
   280             IntPtr preventMediaRemovalParam = MarshalToPointer(preventMediaRemoval);
   281 
   282             bool result = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_MEDIA_REMOVAL, preventMediaRemovalParam, Convert.ToUInt32(Marshal.SizeOf(preventMediaRemoval)), IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
   283             CheckLastError("Media removal: ");
   284             Marshal.FreeHGlobal(preventMediaRemovalParam);
   285 
   286             return result;
   287         }
   288 
   289         /// <summary>
   290         /// Eject optical drive media opening the tray if any.
   291         /// </summary>
   292         /// <param name="aVolume"></param>
   293         /// <returns></returns>
   294         private bool MediaEject(SafeFileHandle aVolume)
   295         {
   296             //Hope that's doing what I think it does
   297             IntPtr dwBytesReturned = new IntPtr();
   298             //Should not be needed but I'm not sure how to pass NULL in there.
   299             OVERLAPPED overlapped=new OVERLAPPED();
   300 
   301             bool res = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_EJECT_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
   302             CheckLastError("Media eject: ");
   303             return res;
   304         }
   305 
   306         /// <summary>
   307         /// Close an optical drive tray.
   308         /// </summary>
   309         /// <param name="aVolume"></param>
   310         /// <returns></returns>
   311         private bool MediaLoad(SafeFileHandle aVolume)
   312         {
   313             //Hope that's doing what I think it does
   314             IntPtr dwBytesReturned = new IntPtr();
   315             //Should not be needed but I'm not sure how to pass NULL in there.
   316             OVERLAPPED overlapped=new OVERLAPPED();
   317 
   318             bool res = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_LOAD_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
   319             CheckLastError("Media load: ");
   320             return res;
   321         }
   322 
   323         /// <summary>
   324         /// 
   325         /// </summary>
   326         /// <param name="aVolume"></param>
   327         /// <returns></returns>
   328         private bool StorageCheckVerify(SafeFileHandle aVolume)
   329         {
   330             //Hope that's doing what I think it does
   331             IntPtr dwBytesReturned = new IntPtr();
   332             //Should not be needed but I'm not sure how to pass NULL in there.
   333             OVERLAPPED overlapped = new OVERLAPPED();
   334 
   335             bool res = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_CHECK_VERIFY2, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
   336 
   337             CheckLastError("Check verify: ");
   338 
   339             return res;
   340         }        
   341         
   342 
   343 
   344         /// <summary>
   345         /// Perform media ejection.
   346         /// </summary>
   347         private void HandleEject()
   348         {
   349             string drive = ((MainForm)this).OpticalDriveToEject();
   350             if (drive.Length!=2)
   351             {
   352                 //Not a proper drive spec.
   353                 //Probably 'None' selected.
   354                 return;
   355             }
   356 
   357             SafeFileHandle handle = OpenVolume(drive);
   358             if (handle.IsInvalid)
   359             {
   360                 CheckLastError("ERROR: Failed to open volume: ");
   361                 return;
   362             }
   363 
   364             if (LockVolume(handle) && DismountVolume(handle))
   365             {
   366                 Debug.WriteLine("Volume was dismounted.");
   367 
   368                 if (PreventRemovalOfVolume(handle,false))
   369                 {
   370                     //StorageCheckVerify(handle);
   371 
   372                     DateTime before;
   373                     before = DateTime.Now;
   374                     bool ejectSuccess = MediaEject(handle);
   375                     double ms = (DateTime.Now - before).TotalMilliseconds;
   376 
   377                     //We assume that if it take more than a certain time to for eject to execute it means we actually ejected.
   378                     //If our eject completes too rapidly we assume the tray is already open and we will try to close it. 
   379                     if (ejectSuccess && ms > 100)
   380                     {
   381                         Debug.WriteLine("Media was ejected");
   382                     }
   383                     else if (MediaLoad(handle))
   384                     {
   385                         Debug.WriteLine("Media was loaded");
   386                     }                    
   387                 }
   388             }
   389             else
   390             {
   391                 Debug.WriteLine("Volume lock or dismount failed.");
   392             }
   393 
   394             //This is needed to make sure we can open the volume next time around
   395             handle.Dispose();
   396         }
   397 
   398         /// <summary>
   399         /// 
   400         /// </summary>
   401         private void HandleGreenStart()
   402         {
   403             //First check if the process we want to launch already exists
   404             string procName = Path.GetFileNameWithoutExtension(Properties.Settings.Default.StartFileName);
   405             Process[] existingProcesses = Process.GetProcessesByName(procName);
   406             if (existingProcesses == null || existingProcesses.Length == 0)
   407             {
   408                 // Process do not exists just try to launch it
   409                 ProcessStartInfo start = new ProcessStartInfo();
   410                 // Enter in the command line arguments, everything you would enter after the executable name itself
   411                 //start.Arguments = arguments; 
   412                 // Enter the executable to run, including the complete path
   413                 start.FileName = Properties.Settings.Default.StartFileName;
   414                 start.WindowStyle = ProcessWindowStyle.Normal;
   415                 start.CreateNoWindow = true;
   416                 start.UseShellExecute = true;
   417                 // Run the external process & wait for it to finish
   418                 Process proc = Process.Start(start);
   419 
   420                 //SL: We could have used that too
   421                 //Shell32.Shell shell = new Shell32.Shell();
   422                 //shell.ShellExecute(Properties.Settings.Default.StartFileName);
   423             }
   424             else
   425             {
   426                 //This won't work properly until we have a manifest that enables uiAccess.
   427                 //However uiAccess just won't work with ClickOnce so we will have to use a different deployment system.
   428                 SwitchToThisWindow(existingProcesses[0].MainWindowHandle, true);
   429             }            
   430         }
   431         /// <summary>
   432         /// We need to handle WM_INPUT.
   433         /// </summary>
   434         /// <param name="message"></param>
   435         protected override void WndProc(ref Message message)
   436         {
   437             switch (message.Msg)
   438             {
   439                 case Const.WM_INPUT:
   440                     //Returning zero means we processed that message.
   441                     message.Result = new IntPtr(0);
   442                     iHidHandler.ProcessInput(ref message);
   443                     break;
   444             }
   445 
   446             //Hook in our power manager
   447             if (iPowerSettingNotifier!=null)
   448             {
   449                 iPowerSettingNotifier.WndProc(ref message);
   450             }
   451 
   452             //Is that needed? Check the docs.
   453             base.WndProc(ref message);
   454         }
   455     }
   456 }