All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jline.terminal.impl.jni.win.NativeWinSysTerminal Maven / Gradle / Ivy

/*
 * Copyright (c) 2002-2020, the original author(s).
 *
 * This software is distributable under the BSD license. See the terms of the
 * BSD license in the documentation provided with this software.
 *
 * https://opensource.org/licenses/BSD-3-Clause
 */
package org.jline.terminal.impl.jni.win;

import java.io.BufferedWriter;
import java.io.IOError;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.function.IntConsumer;

import org.jline.nativ.Kernel32;
import org.jline.nativ.Kernel32.CONSOLE_SCREEN_BUFFER_INFO;
import org.jline.nativ.Kernel32.INPUT_RECORD;
import org.jline.nativ.Kernel32.KEY_EVENT_RECORD;
import org.jline.terminal.Cursor;
import org.jline.terminal.Size;
import org.jline.terminal.impl.AbstractWindowsTerminal;
import org.jline.terminal.spi.SystemStream;
import org.jline.terminal.spi.TerminalProvider;
import org.jline.utils.InfoCmp;
import org.jline.utils.OSUtils;

import static org.jline.nativ.Kernel32.FORMAT_MESSAGE_FROM_SYSTEM;
import static org.jline.nativ.Kernel32.FormatMessageW;
import static org.jline.nativ.Kernel32.GetConsoleScreenBufferInfo;
import static org.jline.nativ.Kernel32.GetLastError;
import static org.jline.nativ.Kernel32.GetStdHandle;
import static org.jline.nativ.Kernel32.INVALID_HANDLE_VALUE;
import static org.jline.nativ.Kernel32.STD_ERROR_HANDLE;
import static org.jline.nativ.Kernel32.STD_INPUT_HANDLE;
import static org.jline.nativ.Kernel32.STD_OUTPUT_HANDLE;
import static org.jline.nativ.Kernel32.WaitForSingleObject;
import static org.jline.nativ.Kernel32.readConsoleInputHelper;

public class NativeWinSysTerminal extends AbstractWindowsTerminal {

    private static final long consoleIn = GetStdHandle(STD_INPUT_HANDLE);
    private static final long consoleOut = GetStdHandle(STD_OUTPUT_HANDLE);
    private static final long consoleErr = GetStdHandle(STD_ERROR_HANDLE);

    public static NativeWinSysTerminal createTerminal(
            TerminalProvider provider,
            SystemStream systemStream,
            String name,
            String type,
            boolean ansiPassThrough,
            Charset encoding,
            boolean nativeSignals,
            SignalHandler signalHandler,
            boolean paused)
            throws IOException {
        // Get input console mode
        int[] inMode = new int[1];
        if (Kernel32.GetConsoleMode(consoleIn, inMode) == 0) {
            throw new IOException("Failed to get console mode: " + getLastErrorMessage());
        }
        // Get output console and mode
        long console = getConsole(systemStream);
        int[] outMode = new int[1];
        if (Kernel32.GetConsoleMode(console, outMode) == 0) {
            throw new IOException("Failed to get console mode: " + getLastErrorMessage());
        }
        // Create writer
        Writer writer;
        if (ansiPassThrough) {
            type = type != null ? type : OSUtils.IS_CONEMU ? TYPE_WINDOWS_CONEMU : TYPE_WINDOWS;
            writer = newConsoleWriter(console);
        } else {
            if (enableVtp(console, outMode[0])) {
                type = type != null ? type : TYPE_WINDOWS_VTP;
                writer = newConsoleWriter(console);
            } else if (OSUtils.IS_CONEMU) {
                type = type != null ? type : TYPE_WINDOWS_CONEMU;
                writer = newConsoleWriter(console);
            } else {
                type = type != null ? type : TYPE_WINDOWS;
                writer = new WindowsAnsiWriter(new BufferedWriter(newConsoleWriter(console)));
            }
        }
        // Create terminal
        NativeWinSysTerminal terminal = new NativeWinSysTerminal(
                provider,
                systemStream,
                writer,
                name,
                type,
                encoding,
                nativeSignals,
                signalHandler,
                consoleIn,
                inMode[0],
                console,
                outMode[0]);
        // Start input pump thread
        if (!paused) {
            terminal.resume();
        }
        return terminal;
    }

    public static long getConsole(SystemStream systemStream) {
        long console;
        switch (systemStream) {
            case Output:
                console = consoleOut;
                break;
            case Error:
                console = consoleErr;
                break;
            default:
                throw new IllegalArgumentException("Unsupported stream for console: " + systemStream);
        }
        return console;
    }

    private static boolean enableVtp(long console, int outMode) {
        return Kernel32.SetConsoleMode(console, outMode | AbstractWindowsTerminal.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
                != 0;
    }

    private static Writer newConsoleWriter(long console) {
        return new NativeWinConsoleWriter(console);
    }

    public static boolean isWindowsSystemStream(SystemStream stream) {
        int[] mode = new int[1];
        long console;
        switch (stream) {
            case Input:
                console = consoleIn;
                break;
            case Output:
                console = consoleOut;
                break;
            case Error:
                console = consoleErr;
                break;
            default:
                return false;
        }
        return Kernel32.GetConsoleMode(console, mode) != 0;
    }

    NativeWinSysTerminal(
            TerminalProvider provider,
            SystemStream systemStream,
            Writer writer,
            String name,
            String type,
            Charset encoding,
            boolean nativeSignals,
            SignalHandler signalHandler,
            long inConsole,
            int inMode,
            long outConsole,
            int outMode)
            throws IOException {
        super(
                provider,
                systemStream,
                writer,
                name,
                type,
                encoding,
                nativeSignals,
                signalHandler,
                inConsole,
                inMode,
                outConsole,
                outMode);
    }

    @Override
    protected int getConsoleMode(Long console) {
        int[] mode = new int[1];
        if (Kernel32.GetConsoleMode(console, mode) == 0) {
            return -1;
        }
        return mode[0];
    }

    @Override
    protected void setConsoleMode(Long console, int mode) {
        Kernel32.SetConsoleMode(console, mode);
    }

    public Size getSize() {
        CONSOLE_SCREEN_BUFFER_INFO info = new CONSOLE_SCREEN_BUFFER_INFO();
        Kernel32.GetConsoleScreenBufferInfo(outConsole, info);
        return new Size(info.windowWidth(), info.windowHeight());
    }

    @Override
    public Size getBufferSize() {
        CONSOLE_SCREEN_BUFFER_INFO info = new CONSOLE_SCREEN_BUFFER_INFO();
        Kernel32.GetConsoleScreenBufferInfo(outConsole, info);
        return new Size(info.size.x, info.size.y);
    }

    protected boolean processConsoleInput() throws IOException {
        INPUT_RECORD[] events;
        if (inConsole != INVALID_HANDLE_VALUE && WaitForSingleObject(inConsole, 100) == 0) {
            events = readConsoleInputHelper(inConsole, 1, false);
        } else {
            return false;
        }

        boolean flush = false;
        for (INPUT_RECORD event : events) {
            if (event.eventType == INPUT_RECORD.KEY_EVENT) {
                KEY_EVENT_RECORD keyEvent = event.keyEvent;
                processKeyEvent(keyEvent.keyDown, keyEvent.keyCode, keyEvent.uchar, keyEvent.controlKeyState);
                flush = true;
            } else if (event.eventType == INPUT_RECORD.WINDOW_BUFFER_SIZE_EVENT) {
                raise(Signal.WINCH);
            } else if (event.eventType == INPUT_RECORD.MOUSE_EVENT) {
                processMouseEvent(event.mouseEvent);
                flush = true;
            } else if (event.eventType == INPUT_RECORD.FOCUS_EVENT) {
                processFocusEvent(event.focusEvent.setFocus);
            }
        }

        return flush;
    }

    private char[] focus = new char[] {'\033', '[', ' '};

    private void processFocusEvent(boolean hasFocus) throws IOException {
        if (focusTracking) {
            focus[2] = hasFocus ? 'I' : 'O';
            slaveInputPipe.write(focus);
        }
    }

    private char[] mouse = new char[] {'\033', '[', 'M', ' ', ' ', ' '};

    private void processMouseEvent(Kernel32.MOUSE_EVENT_RECORD mouseEvent) throws IOException {
        int dwEventFlags = mouseEvent.eventFlags;
        int dwButtonState = mouseEvent.buttonState;
        if (tracking == MouseTracking.Off
                || tracking == MouseTracking.Normal && dwEventFlags == Kernel32.MOUSE_EVENT_RECORD.MOUSE_MOVED
                || tracking == MouseTracking.Button
                        && dwEventFlags == Kernel32.MOUSE_EVENT_RECORD.MOUSE_MOVED
                        && dwButtonState == 0) {
            return;
        }
        int cb = 0;
        dwEventFlags &= ~Kernel32.MOUSE_EVENT_RECORD.DOUBLE_CLICK; // Treat double-clicks as normal
        if (dwEventFlags == Kernel32.MOUSE_EVENT_RECORD.MOUSE_WHEELED) {
            cb |= 64;
            if ((dwButtonState >> 16) < 0) {
                cb |= 1;
            }
        } else if (dwEventFlags == Kernel32.MOUSE_EVENT_RECORD.MOUSE_HWHEELED) {
            return;
        } else if ((dwButtonState & Kernel32.MOUSE_EVENT_RECORD.FROM_LEFT_1ST_BUTTON_PRESSED) != 0) {
            cb |= 0x00;
        } else if ((dwButtonState & Kernel32.MOUSE_EVENT_RECORD.RIGHTMOST_BUTTON_PRESSED) != 0) {
            cb |= 0x01;
        } else if ((dwButtonState & Kernel32.MOUSE_EVENT_RECORD.FROM_LEFT_2ND_BUTTON_PRESSED) != 0) {
            cb |= 0x02;
        } else {
            cb |= 0x03;
        }
        int cx = mouseEvent.mousePosition.x;
        int cy = mouseEvent.mousePosition.y;
        mouse[3] = (char) (' ' + cb);
        mouse[4] = (char) (' ' + cx + 1);
        mouse[5] = (char) (' ' + cy + 1);
        slaveInputPipe.write(mouse);
    }

    @Override
    public Cursor getCursorPosition(IntConsumer discarded) {
        CONSOLE_SCREEN_BUFFER_INFO info = new CONSOLE_SCREEN_BUFFER_INFO();
        if (GetConsoleScreenBufferInfo(outConsole, info) == 0) {
            throw new IOError(new IOException("Could not get the cursor position: " + getLastErrorMessage()));
        }
        return new Cursor(info.cursorPosition.x, info.cursorPosition.y);
    }

    public void disableScrolling() {
        strings.remove(InfoCmp.Capability.insert_line);
        strings.remove(InfoCmp.Capability.parm_insert_line);
        strings.remove(InfoCmp.Capability.delete_line);
        strings.remove(InfoCmp.Capability.parm_delete_line);
    }

    static String getLastErrorMessage() {
        int errorCode = GetLastError();
        return getErrorMessage(errorCode);
    }

    static String getErrorMessage(int errorCode) {
        int bufferSize = 160;
        byte[] data = new byte[bufferSize];
        FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, 0, errorCode, 0, data, bufferSize, null);
        return new String(data, StandardCharsets.UTF_16LE).trim();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy