public void ParseInputEvent(INPUT_RECORD inputRecord, Control rootElement) { if (inputRecord.EventType == EventType.MOUSE_EVENT) { MOUSE_EVENT_RECORD mouseEvent = inputRecord.MouseEvent; if (mouseEvent.dwEventFlags != MouseEventFlags.PRESSED_OR_RELEASED && mouseEvent.dwEventFlags != MouseEventFlags.MOUSE_MOVED && mouseEvent.dwEventFlags != MouseEventFlags.DOUBLE_CLICK && mouseEvent.dwEventFlags != MouseEventFlags.MOUSE_WHEELED && mouseEvent.dwEventFlags != MouseEventFlags.MOUSE_HWHEELED) { // throw new InvalidOperationException("Flags combination in mouse event was not expected."); } Point rawPosition; if (mouseEvent.dwEventFlags == MouseEventFlags.MOUSE_MOVED || mouseEvent.dwEventFlags == MouseEventFlags.PRESSED_OR_RELEASED) { rawPosition = new Point(mouseEvent.dwMousePosition.X, mouseEvent.dwMousePosition.Y); lastMousePosition = rawPosition; } else { // При событии MOUSE_WHEELED в Windows некорректно устанавливается mouseEvent.dwMousePosition // Поэтому для определения элемента, над которым производится прокручивание колёсика, мы // вынуждены сохранять координаты, полученные при предыдущем событии мыши rawPosition = lastMousePosition; } Control topMost = findSource(rawPosition, rootElement); // если мышь захвачена контролом, то события перемещения мыши доставляются только ему, // события, связанные с нажатием мыши - тоже доставляются только ему, вместо того // контрола, над которым событие было зарегистрировано. Такой механизм необходим, // например, для корректной обработки перемещений окон (вверх или в стороны) Control source = (inputCaptureStack.Count != 0) ? inputCaptureStack.Peek() : topMost; if (mouseEvent.dwEventFlags == MouseEventFlags.MOUSE_MOVED) { MouseButtonState leftMouseButtonState = getLeftButtonState(mouseEvent.dwButtonState); MouseButtonState middleMouseButtonState = getMiddleButtonState(mouseEvent.dwButtonState); MouseButtonState rightMouseButtonState = getRightButtonState(mouseEvent.dwButtonState); // MouseEventArgs mouseEventArgs = new MouseEventArgs(source, Control.PreviewMouseMoveEvent, rawPosition, leftMouseButtonState, middleMouseButtonState, rightMouseButtonState ); eventsQueue.Enqueue(mouseEventArgs); // lastLeftMouseButtonState = leftMouseButtonState; lastMiddleMouseButtonState = middleMouseButtonState; lastRightMouseButtonState = rightMouseButtonState; // detect mouse enter / mouse leave events // path to source from root element down List<Control> mouseOverStack = new List<Control>(); Control current = topMost; while (null != current) { mouseOverStack.Insert(0, current); current = current.Parent; } int index; for (index = 0; index < Math.Min(mouseOverStack.Count, prevMouseOverStack.Count); index++) { if (mouseOverStack[index] != prevMouseOverStack[index]) break; } for (int i = prevMouseOverStack.Count - 1; i >= index; i-- ) { Control control = prevMouseOverStack[i]; MouseEventArgs args = new MouseEventArgs(control, Control.MouseLeaveEvent, rawPosition, leftMouseButtonState, middleMouseButtonState, rightMouseButtonState ); eventsQueue.Enqueue(args); } for (int i = index; i < mouseOverStack.Count; i++ ) { // enqueue MouseEnter event Control control = mouseOverStack[i]; MouseEventArgs args = new MouseEventArgs(control, Control.MouseEnterEvent, rawPosition, leftMouseButtonState, middleMouseButtonState, rightMouseButtonState ); eventsQueue.Enqueue(args); } prevMouseOverStack.Clear(); prevMouseOverStack.AddRange(mouseOverStack); } if (mouseEvent.dwEventFlags == MouseEventFlags.PRESSED_OR_RELEASED) { // MouseButtonState leftMouseButtonState = getLeftButtonState(mouseEvent.dwButtonState); MouseButtonState middleMouseButtonState = getMiddleButtonState(mouseEvent.dwButtonState); MouseButtonState rightMouseButtonState = getRightButtonState(mouseEvent.dwButtonState); // if (leftMouseButtonState != lastLeftMouseButtonState) { MouseButtonEventArgs eventArgs = new MouseButtonEventArgs(source, leftMouseButtonState == MouseButtonState.Pressed ? Control.PreviewMouseDownEvent : Control.PreviewMouseUpEvent, rawPosition, leftMouseButtonState, lastMiddleMouseButtonState, lastRightMouseButtonState, MouseButton.Left ); eventsQueue.Enqueue(eventArgs); } if (middleMouseButtonState != lastMiddleMouseButtonState) { MouseButtonEventArgs eventArgs = new MouseButtonEventArgs(source, middleMouseButtonState == MouseButtonState.Pressed ? Control.PreviewMouseDownEvent : Control.PreviewMouseUpEvent, rawPosition, lastLeftMouseButtonState, middleMouseButtonState, lastRightMouseButtonState, MouseButton.Middle ); eventsQueue.Enqueue(eventArgs); } if (rightMouseButtonState != lastRightMouseButtonState) { MouseButtonEventArgs eventArgs = new MouseButtonEventArgs(source, rightMouseButtonState == MouseButtonState.Pressed ? Control.PreviewMouseDownEvent : Control.PreviewMouseUpEvent, rawPosition, lastLeftMouseButtonState, lastMiddleMouseButtonState, rightMouseButtonState, MouseButton.Right ); eventsQueue.Enqueue(eventArgs); } // lastLeftMouseButtonState = leftMouseButtonState; lastMiddleMouseButtonState = middleMouseButtonState; lastRightMouseButtonState = rightMouseButtonState; } if (mouseEvent.dwEventFlags == MouseEventFlags.MOUSE_WHEELED) { MouseWheelEventArgs args = new MouseWheelEventArgs( topMost, Control.PreviewMouseWheelEvent, rawPosition, lastLeftMouseButtonState, lastMiddleMouseButtonState, lastRightMouseButtonState, mouseEvent.dwButtonState > 0 ? 1 : -1 ); eventsQueue.Enqueue( args ); } } if (inputRecord.EventType == EventType.KEY_EVENT) { KEY_EVENT_RECORD keyEvent = inputRecord.KeyEvent; KeyEventArgs eventArgs = new KeyEventArgs( ConsoleApplication.Instance.FocusManager.FocusedElement, keyEvent.bKeyDown ? Control.PreviewKeyDownEvent : Control.PreviewKeyUpEvent); eventArgs.UnicodeChar = keyEvent.UnicodeChar; eventArgs.bKeyDown = keyEvent.bKeyDown; eventArgs.dwControlKeyState = keyEvent.dwControlKeyState; eventArgs.wRepeatCount = keyEvent.wRepeatCount; eventArgs.wVirtualKeyCode = keyEvent.wVirtualKeyCode; eventArgs.wVirtualScanCode = keyEvent.wVirtualScanCode; eventsQueue.Enqueue(eventArgs); } }
private void runLinux(Control control) { this.mainControl = control; if ( userCanvasSize.IsEmpty ) { // Create physical canvas with actual terminal size winsize ws = Libc.GetTerminalSize( isDarwin ); canvas = new PhysicalCanvas( ws.ws_col, ws.ws_row ); } else { canvas = new PhysicalCanvas( userCanvasSize.Width, userCanvasSize.Height ); } renderer.Canvas = canvas; renderer.RootElementRect = userRootElementRect.IsEmpty ? new Rect( canvas.Size ) : userRootElementRect; renderer.RootElement = mainControl; // mainControl.Invalidate (); // Terminal initialization sequence // This is magic workaround to avoid messing up terminal after program finish // The bug is described at https://bugzilla.xamarin.com/show_bug.cgi?id=15118 bool ignored = Console.KeyAvailable; IntPtr stdscr = NCurses.initscr (); NCurses.cbreak (); NCurses.noecho (); NCurses.nonl (); NCurses.intrflush (stdscr, false); NCurses.keypad (stdscr, true); NCurses.start_color (); HideCursor (); try { renderer.UpdateLayout( ); renderer.FinallyApplyChangesToCanvas( ); termkeyHandle = LibTermKey.termkey_new( 0, TermKeyFlag.TERMKEY_FLAG_SPACESYMBOL ); // Setup the input mode Console.Write( "\x1B[?1002h" ); pollfd fd = new pollfd( ); fd.fd = 0; fd.events = POLL_EVENTS.POLLIN; pollfd[ ] fds = new pollfd[ 2 ]; fds[ 0 ] = fd; fds[ 1 ] = new pollfd( ); int pipeResult = Libc.pipe( pipeFds ); if ( pipeResult == -1 ) { throw new InvalidOperationException( "Cannot create self-pipe." ); } fds[ 1 ].fd = pipeFds[ 0 ]; fds[ 1 ].events = POLL_EVENTS.POLLIN; try { #if !WIN32 // Catch SIGWINCH to handle terminal resizing UnixSignal[] signals = new UnixSignal [] { new UnixSignal (Signum.SIGWINCH) }; Thread signal_thread = new Thread (delegate () { while (true) { // Wait for a signal to be delivered int index = UnixSignal.WaitAny (signals, -1); Signum signal = signals [index].Signum; Libc.writeInt64 (pipeFds[1], 2); } } ); signal_thread.IsBackground = false; signal_thread.Start (); #endif TermKeyKey key = new TermKeyKey( ); // this.running = true; this.mainThreadId = Thread.CurrentThread.ManagedThreadId; // int nextwait = -1; while ( true ) { int pollRes = Libc.poll( fds, 2, nextwait ); if ( pollRes == 0 ) { if (nextwait == -1) throw new InvalidOperationException( "Assertion failed." ); if (TermKeyResult.TERMKEY_RES_KEY == LibTermKey.termkey_getkey_force(termkeyHandle, ref key) ) { processLinuxInput( key ); } } if ( pollRes == -1 ) { int errorCode = Marshal.GetLastWin32Error(); if ( errorCode != Libc.EINTR ) { throw new InvalidOperationException(string.Format("poll() returned with error code {0}", errorCode)); } } if ( fds[ 1 ].revents != POLL_EVENTS.NONE ) { UInt64 u; Libc.readInt64( fds[ 1 ].fd, out u ); if ( u == 1 ) { // Exit from application #if !WIN32 signal_thread.Abort (); #endif break; } if ( u == 2 ) { // Get new term size and process appropriate INPUT_RECORD event INPUT_RECORD inputRecord = new INPUT_RECORD( ); inputRecord.EventType = EventType.WINDOW_BUFFER_SIZE_EVENT; winsize ws = Libc.GetTerminalSize( isDarwin ); inputRecord.WindowBufferSizeEvent.dwSize.X = ( short ) ws.ws_col; inputRecord.WindowBufferSizeEvent.dwSize.Y = ( short ) ws.ws_row; processInputEvent( inputRecord ); } if (u == 3 ) { // It is signal from async actions invocation stuff } } if ( ( fds[ 0 ].revents & POLL_EVENTS.POLLIN ) == POLL_EVENTS.POLLIN || ( fds[ 0 ].revents & POLL_EVENTS.POLLHUP ) == POLL_EVENTS.POLLHUP || ( fds[ 0 ].revents & POLL_EVENTS.POLLERR ) == POLL_EVENTS.POLLERR ) { LibTermKey.termkey_advisereadable( termkeyHandle ); } TermKeyResult result = ( LibTermKey.termkey_getkey( termkeyHandle, ref key ) ); while (result == TermKeyResult.TERMKEY_RES_KEY ) { processLinuxInput( key ); result = ( LibTermKey.termkey_getkey( termkeyHandle, ref key ) ); } if (result == TermKeyResult.TERMKEY_RES_AGAIN) { nextwait = LibTermKey.termkey_get_waittime(termkeyHandle); } else { nextwait = -1; } while ( true ) { bool anyInvokeActions = isAnyInvokeActions( ); bool anyRoutedEvent = !EventManager.IsQueueEmpty( ); bool anyLayoutToRevalidate = renderer.AnyControlInvalidated; if (!anyInvokeActions && !anyRoutedEvent && !anyLayoutToRevalidate) break; EventManager.ProcessEvents(); processInvokeActions( ); renderer.UpdateLayout( ); } renderer.FinallyApplyChangesToCanvas( ); } } finally { LibTermKey.termkey_destroy( termkeyHandle ); Libc.close( pipeFds[ 0 ] ); Libc.close( pipeFds[ 1 ] ); Console.Write( "\x1B[?1002l" ); } } finally { // Restore cursor visibility before exit ShowCursor( ); NCurses.endwin( ); } renderer.RootElement = null; }
private void processLinuxInput(TermKeyKey key) { // If any special button has been pressed (Tab, Enter, etc) // we should convert its code to INPUT_RECORD.KeyEvent // Because INPUT_RECORD.KeyEvent depends on Windows' scan codes, // we convert codes retrieved from LibTermKey to Windows virtual scan codes // In the future, this logic may be changed (for example, both Windows and Linux // raw codes can be converted into ConsoleFramework's own abstract enum) if (key.type == TermKeyType.TERMKEY_TYPE_KEYSYM) { INPUT_RECORD inputRecord = new INPUT_RECORD (); inputRecord.EventType = EventType.KEY_EVENT; inputRecord.KeyEvent.bKeyDown = true; inputRecord.KeyEvent.wRepeatCount = 1; switch (key.code.sym) { case TermKeySym.TERMKEY_SYM_TAB: inputRecord.KeyEvent.wVirtualKeyCode = VirtualKeys.Tab; break; case TermKeySym.TERMKEY_SYM_ENTER: inputRecord.KeyEvent.wVirtualKeyCode = VirtualKeys.Return; break; // in gnome-terminal it is backspace by default // (see default compatibility settings in Profile's settings) case TermKeySym.TERMKEY_SYM_DEL: case TermKeySym.TERMKEY_SYM_BACKSPACE: inputRecord.KeyEvent.wVirtualKeyCode = VirtualKeys.Back; break; case TermKeySym.TERMKEY_SYM_DELETE: inputRecord.KeyEvent.wVirtualKeyCode = VirtualKeys.Delete; break; case TermKeySym.TERMKEY_SYM_HOME: inputRecord.KeyEvent.wVirtualKeyCode = VirtualKeys.Home; break; case TermKeySym.TERMKEY_SYM_END: inputRecord.KeyEvent.wVirtualKeyCode = VirtualKeys.End; break; case TermKeySym.TERMKEY_SYM_PAGEUP: inputRecord.KeyEvent.wVirtualKeyCode = VirtualKeys.Prior; break; case TermKeySym.TERMKEY_SYM_PAGEDOWN: inputRecord.KeyEvent.wVirtualKeyCode = VirtualKeys.Next; break; case TermKeySym.TERMKEY_SYM_SPACE: inputRecord.KeyEvent.UnicodeChar = ' '; inputRecord.KeyEvent.wVirtualKeyCode = VirtualKeys.Space; break; case TermKeySym.TERMKEY_SYM_ESCAPE: inputRecord.KeyEvent.wVirtualKeyCode = VirtualKeys.Escape; break; case TermKeySym.TERMKEY_SYM_INSERT: inputRecord.KeyEvent.wVirtualKeyCode = VirtualKeys.Insert; break; case TermKeySym.TERMKEY_SYM_UP: inputRecord.KeyEvent.wVirtualKeyCode = VirtualKeys.Up; break; case TermKeySym.TERMKEY_SYM_DOWN: inputRecord.KeyEvent.wVirtualKeyCode = VirtualKeys.Down; break; case TermKeySym.TERMKEY_SYM_LEFT: inputRecord.KeyEvent.wVirtualKeyCode = VirtualKeys.Left; break; case TermKeySym.TERMKEY_SYM_RIGHT: inputRecord.KeyEvent.wVirtualKeyCode = VirtualKeys.Right; break; default: throw new NotSupportedException ("Not supported keyboard code detected: " + key.code.sym); } inputRecord.KeyEvent.dwControlKeyState = 0; if ((key.modifiers & 4) == 4) { inputRecord.KeyEvent.dwControlKeyState |= ControlKeyState.LEFT_CTRL_PRESSED; } if ((key.modifiers & 2) == 2) { inputRecord.KeyEvent.dwControlKeyState |= ControlKeyState.LEFT_ALT_PRESSED; } processInputEvent (inputRecord); } else if (key.type == TermKeyType.TERMKEY_TYPE_UNICODE) { byte[] data = new byte[7]; data[0] = key.utf8_0; data[1] = key.utf8_1; data[2] = key.utf8_2; data[3] = key.utf8_3; data[4] = key.utf8_4; data[5] = key.utf8_5; data[6] = key.utf8_6; string d = System.Text.Encoding.UTF8.GetString(data); char unicodeCharacter = d[0]; INPUT_RECORD inputRecord = new INPUT_RECORD(); inputRecord.EventType = EventType.KEY_EVENT; inputRecord.KeyEvent.bKeyDown = true; inputRecord.KeyEvent.wRepeatCount = 1; inputRecord.KeyEvent.UnicodeChar = unicodeCharacter; inputRecord.KeyEvent.dwControlKeyState = 0; if (char.IsLetterOrDigit( unicodeCharacter )) { if (char.IsDigit( unicodeCharacter)) { inputRecord.KeyEvent.wVirtualKeyCode = (VirtualKeys) (unicodeCharacter - '0' + (int) VirtualKeys.N0); } else { char lowercased = char.ToLowerInvariant( unicodeCharacter ); // Only english characters can be converted to VirtualKeys if (lowercased >= 'a' && lowercased <= 'z') { inputRecord.KeyEvent.wVirtualKeyCode = (VirtualKeys) (lowercased - 'a' + (int) VirtualKeys.A ); } } } if ((key.modifiers & 4) == 4) { inputRecord.KeyEvent.dwControlKeyState |= ControlKeyState.LEFT_CTRL_PRESSED; } if ((key.modifiers & 2) == 2) { inputRecord.KeyEvent.dwControlKeyState |= ControlKeyState.LEFT_ALT_PRESSED; } // todo : remove hardcoded exit combo after testing if (unicodeCharacter == 'd' && key.modifiers == 4) { Exit (); } processInputEvent(inputRecord); // } else if (key.type == TermKeyType.TERMKEY_TYPE_MOUSE) { TermKeyMouseEvent ev; int button; int line, col; LibTermKey.termkey_interpret_mouse(termkeyHandle, ref key, out ev, out button, out line, out col); // INPUT_RECORD inputRecord = new INPUT_RECORD(); inputRecord.EventType = EventType.MOUSE_EVENT; if (ev == TermKeyMouseEvent.TERMKEY_MOUSE_PRESS || ev == TermKeyMouseEvent.TERMKEY_MOUSE_RELEASE) inputRecord.MouseEvent.dwEventFlags = MouseEventFlags.PRESSED_OR_RELEASED; if (ev == TermKeyMouseEvent.TERMKEY_MOUSE_DRAG) inputRecord.MouseEvent.dwEventFlags = MouseEventFlags.MOUSE_MOVED; inputRecord.MouseEvent.dwMousePosition = new COORD((short) (col - 1), (short) (line - 1)); if (ev == TermKeyMouseEvent.TERMKEY_MOUSE_RELEASE) { inputRecord.MouseEvent.dwButtonState = 0; } else if (ev == TermKeyMouseEvent.TERMKEY_MOUSE_DRAG || ev == TermKeyMouseEvent.TERMKEY_MOUSE_PRESS) { if (1 == button) { inputRecord.MouseEvent.dwButtonState = MOUSE_BUTTON_STATE.FROM_LEFT_1ST_BUTTON_PRESSED; } else if (2 == button) { inputRecord.MouseEvent.dwButtonState = MOUSE_BUTTON_STATE.FROM_LEFT_2ND_BUTTON_PRESSED; } else if (3 == button) { inputRecord.MouseEvent.dwButtonState = MOUSE_BUTTON_STATE.RIGHTMOST_BUTTON_PRESSED; } } // processInputEvent(inputRecord); } }
private void processInputEvent(INPUT_RECORD inputRecord) { if ( inputRecord.EventType == EventType.WINDOW_BUFFER_SIZE_EVENT ) { if ( usingLinux ) { // Reinitializing ncurses to deal with new dimensions // http://stackoverflow.com/questions/13707137/ncurses-resizing-glitch NCurses.endwin(); // Needs to be called after an endwin() so ncurses will initialize // itself with the new terminal dimensions. NCurses.refresh(); NCurses.clear(); } COORD dwSize = inputRecord.WindowBufferSizeEvent.dwSize; // Invoke default handler if no custom handlers attached and // userCanvasSize and userRootElementRect are not defined if ( TerminalSizeChanged == null && userCanvasSize.IsEmpty && userRootElementRect.IsEmpty ) { OnTerminalSizeChangedDefault(this, new TerminalSizeChangedEventArgs( dwSize.X, dwSize.Y )); } else if ( TerminalSizeChanged != null ) { TerminalSizeChanged.Invoke(this, new TerminalSizeChangedEventArgs(dwSize.X, dwSize.Y)); } // Refresh whole display renderer.FinallyApplyChangesToCanvas( true ); return; } eventManager.ParseInputEvent(inputRecord, mainControl); }
private void processInput() { INPUT_RECORD[] buffer = new INPUT_RECORD[10]; uint read; bool bReaded = Win32.ReadConsoleInput(stdInputHandle, buffer, (uint) buffer.Length, out read); if (!bReaded) { throw new InvalidOperationException("ReadConsoleInput method failed."); } for (int i = 0; i < read; ++i) { processInputEvent(buffer[i]); } }
public UserInputEventArgs( INPUT_RECORD inputRecord ) { this.inputRecord = inputRecord; }