private static void Chord(ConsoleKeyInfo?key = null, object arg = null) { if (!key.HasValue) { throw new ArgumentNullException("key"); } Dictionary <ConsoleKeyInfo, KeyHandler> secondKeyDispatchTable; if (_singleton._chordDispatchTable.TryGetValue(key.Value, out secondKeyDispatchTable)) { if (_singleton._demoMode) { // Render so the first key of the chord appears in the demo window _singleton.Render(); } var secondKey = ReadKey(); _singleton.ProcessOneKey(secondKey, secondKeyDispatchTable, ignoreIfNoAction: true, arg: arg); } }
private static ConsoleKeyInfo ReadKey() { // Reading a key is handled on a different thread. During process shutdown, // PowerShell will wait in it's ConsoleCtrlHandler until the pipeline has completed. // If we're running, we're most likely blocked waiting for user input. // This is a problem for two reasons. First, exiting takes a long time (5 seconds // on Win8) because PowerShell is waiting forever, but the OS will forcibly terminate // the console. Also - if there are any event handlers for the engine event // PowerShell.Exiting, those handlers won't get a chance to run. // // By waiting for a key on a different thread, our pipeline execution thread // (the thread Readline is called from) avoid being blocked in code that can't // be unblocked and instead blocks on events we control. // First, set an event so the thread to read a key actually attempts to read a key. _singleton._readKeyWaitHandle.Set(); int handleId; PowerShell ps = null; try { while (true) { // Next, wait for one of three things: // - a key is pressed // - the console is exiting // - 300ms - to process events if we're idle handleId = WaitHandle.WaitAny(_singleton._requestKeyWaitHandles, 300); if (handleId != WaitHandle.WaitTimeout) { break; } // If we timed out, check for event subscribers (which is just // a hint that there might be an event waiting to be processed.) var eventSubscribers = _singleton._engineIntrinsics.Events.Subscribers; if (eventSubscribers.Count > 0) { bool runPipelineForEventProcessing = false; foreach (var sub in eventSubscribers) { if (sub.SourceIdentifier.Equals("PowerShell.OnIdle", StringComparison.OrdinalIgnoreCase)) { // There is an OnIdle event. We're idle because we timed out. Normally // PowerShell generates this event, but PowerShell assumes the engine is not // idle because it called PSConsoleHostReadline which isn't returning. // So we generate the event intstead. _singleton._engineIntrinsics.Events.GenerateEvent("PowerShell.OnIdle", null, null, null); runPipelineForEventProcessing = true; break; } // If there are any event subscribers that have an action (which might // write to the console) and have a source object (i.e. aren't engine // events), run a tiny useless bit of PowerShell so that the events // can be processed. if (sub.Action != null && sub.SourceObject != null) { runPipelineForEventProcessing = true; break; } } if (runPipelineForEventProcessing) { if (ps == null) { ps = PowerShell.Create(RunspaceMode.CurrentRunspace); ps.AddScript("0"); } // To detect output during possible event processing, see if the cursor moved // and rerender if so. var y = Console.CursorTop; ps.Invoke(); if (y != Console.CursorTop) { _singleton._initialY = Console.CursorTop; _singleton.Render(); } } } } } finally { if (ps != null) { ps.Dispose(); } } if (handleId == 1) { // The console is exiting - throw an exception to unwind the stack to the point // where we can return from ReadLine. if (_singleton.Options.HistorySaveStyle == HistorySaveStyle.SaveAtExit) { _singleton.SaveHistoryAtExit(); } _singleton._historyFileMutex.Dispose(); throw new OperationCanceledException(); } var key = _singleton._queuedKeys.Dequeue(); if (_singleton._captureKeys) { _singleton._savedKeys.Enqueue(key); } return(key); }