Persisting selection of optical drive to eject.
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;
12 using Hid = SharpLib.Hid;
15 namespace SharpDisplayManager
18 /// Implement handling of HID input reports notably to be able to launch an application using the Green Start button from IR remotes.
20 [System.ComponentModel.DesignerCategory("Code")]
21 public class MainFormHid : Form
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);
26 public delegate void OnHidEventDelegate(object aSender, Hid.Event aHidEvent);
29 /// Use notably to handle green start key from IR remote control
31 private Hid.Handler iHidHandler;
34 /// Register HID devices so that we receive corresponding WM_INPUT messages.
36 protected void RegisterHidDevices()
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.
42 RAWINPUTDEVICE[] rid = new RAWINPUTDEVICE[5];
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;
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;
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;
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;
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;
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;
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;
87 iHidHandler = new SharpLib.Hid.Handler(rid);
88 if (!iHidHandler.IsRegistered)
90 Debug.WriteLine("Failed to register raw input devices: " + Marshal.GetLastWin32Error().ToString());
92 iHidHandler.OnHidEvent += HandleHidEventThreadSafe;
96 /// Here we receive HID events from our HID library.
98 /// <param name="aSender"></param>
99 /// <param name="aHidEvent"></param>
100 public void HandleHidEventThreadSafe(object aSender, SharpLib.Hid.Event aHidEvent)
102 if (aHidEvent.IsStray)
104 //Stray event just ignore it
108 if (this.InvokeRequired)
110 //Not in the proper thread, invoke ourselves
111 OnHidEventDelegate d = new OnHidEventDelegate(HandleHidEventThreadSafe);
112 this.Invoke(d, new object[] { aSender, aHidEvent });
116 if (aHidEvent.Usages.Count == 0)
118 //No usage, nothing to do then
122 //We are in the proper thread
123 if (aHidEvent.UsagePage == (ushort) Hid.UsagePage.WindowsMediaCenterRemoteControl)
125 switch (aHidEvent.Usages[0])
127 case (ushort)Hid.Usage.WindowsMediaCenterRemoteControl.GreenStart:
130 case (ushort)Hid.Usage.WindowsMediaCenterRemoteControl.Eject:
131 case (ushort)Hid.Usage.WindowsMediaCenterRemoteControl.Ext2:
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)
149 /// <param name="data"></param>
150 /// <returns></returns>
151 private IntPtr MarshalToPointer(object data)
153 IntPtr buf = Marshal.AllocHGlobal(
154 Marshal.SizeOf(data));
155 Marshal.StructureToPtr(data,
163 /// <returns></returns>
164 private SafeFileHandle OpenVolume(string aDriveName)
166 return Function.CreateFile("\\\\.\\" + aDriveName,
167 SharpLib.Win32.FileAccess.GENERIC_READ,
168 SharpLib.Win32.FileShare.FILE_SHARE_READ | SharpLib.Win32.FileShare.FILE_SHARE_WRITE,
170 CreationDisposition.OPEN_EXISTING,
178 /// <param name="aVolume"></param>
179 /// <returns></returns>
180 private bool LockVolume(SafeFileHandle aVolume)
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();
188 const int KMaxTries = 100;
189 const int KSleepTime = 10;
190 bool success = false;
192 while (!success && tries < KMaxTries)
194 success = Function.DeviceIoControl(aVolume, Const.FSCTL_LOCK_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
195 System.Threading.Thread.Sleep(KSleepTime);
205 /// <param name="aVolume"></param>
206 /// <returns></returns>
207 private bool DismountVolume(SafeFileHandle aVolume)
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();
214 return Function.DeviceIoControl(aVolume, Const.FSCTL_DISMOUNT_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
222 /// <param name="aVolume"></param>
223 /// <param name="aPreventRemoval"></param>
224 /// <returns></returns>
225 private bool PreventRemovalOfVolume(SafeFileHandle aVolume, bool aPreventRemoval)
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();
232 PREVENT_MEDIA_REMOVAL preventMediaRemoval = new PREVENT_MEDIA_REMOVAL();
233 preventMediaRemoval.PreventMediaRemoval = Convert.ToByte(aPreventRemoval);
234 IntPtr preventMediaRemovalParam = MarshalToPointer(preventMediaRemoval);
236 bool result = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_MEDIA_REMOVAL, preventMediaRemovalParam, Convert.ToUInt32(Marshal.SizeOf(preventMediaRemoval)), IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
238 Marshal.FreeHGlobal(preventMediaRemovalParam);
246 /// <param name="aVolume"></param>
247 /// <returns></returns>
248 private bool AutoEjectVolume(SafeFileHandle aVolume)
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();
255 return Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_EJECT_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
261 /// <param name="aVolume"></param>
262 /// <returns></returns>
263 private bool CloseTray(SafeFileHandle aVolume)
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();
270 return Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_LOAD_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
278 /// Perform media ejection.
280 private void HandleEject()
282 string drive = ((MainForm)this).OpticalDriveToEject();
285 //Not a proper drive spec.
286 //Probably 'None' selected.
290 SafeFileHandle handle = OpenVolume(drive);
291 if (handle.IsInvalid)
296 if (LockVolume(handle) && DismountVolume(handle))
298 Debug.Write("Volume was dismounted.");
300 if (PreventRemovalOfVolume(handle,false))
302 if (AutoEjectVolume(handle))
304 Debug.Write("Media was ejected");
306 //else if (CloseTray(handle))
308 // Debug.Write("Media was loaded");
317 private void HandleGreenStart()
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)
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);
336 //SL: We could have used that too
337 //Shell32.Shell shell = new Shell32.Shell();
338 //shell.ShellExecute(Properties.Settings.Default.StartFileName);
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);
348 /// We need to handle WM_INPUT.
350 /// <param name="message"></param>
351 protected override void WndProc(ref Message message)
356 //Returning zero means we processed that message.
357 message.Result = new IntPtr(0);
358 iHidHandler.ProcessInput(ref message);
361 //Is that needed? Check the docs.
362 base.WndProc(ref message);