diff --git a/terminal/src/main/java/org/jline/terminal/impl/jep424/CLibrary.java b/terminal/src/main/java/org/jline/terminal/impl/jep424/CLibrary.java new file mode 100644 index 000000000..1a04cdfbf --- /dev/null +++ b/terminal/src/main/java/org/jline/terminal/impl/jep424/CLibrary.java @@ -0,0 +1,1150 @@ +/* + * Copyright (C) 2022 the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jline.terminal.impl.jep424; + +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.GroupLayout; +import java.lang.foreign.Linker; +import java.lang.foreign.MemoryAddress; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.MemorySession; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.VarHandle; +import java.util.EnumMap; +import java.util.EnumSet; + +import org.jline.terminal.Attributes; +import org.jline.terminal.Size; +import org.jline.terminal.spi.Pty; + +class CLibrary +{ + // Window sizes. + // @see IOCTL_TTY(2) man-page + static class winsize + { + static GroupLayout layout; + static VarHandle ws_col; + static VarHandle ws_row; + + static + { + layout = MemoryLayout.structLayout( + ValueLayout.JAVA_SHORT.withName( "ws_row" ), + ValueLayout.JAVA_SHORT.withName( "ws_col" ), + ValueLayout.JAVA_SHORT, + ValueLayout.JAVA_SHORT + ); + ws_row = layout.varHandle( MemoryLayout.PathElement.groupElement( "ws_row" ) ); + ws_col = layout.varHandle( MemoryLayout.PathElement.groupElement( "ws_col" ) ); + } + + private final MemorySegment seg; + + winsize() + { + seg = MemorySegment.allocateNative( layout, MemorySession.openImplicit() ); + } + + winsize( short ws_col, short ws_row ) + { + this(); + ws_col( ws_col ); + ws_row( ws_row ); + } + + MemoryAddress address() + { + return seg.address(); + } + + short ws_col() + { + return (short) ws_col.get( seg.address() ); + } + + void ws_col( short col ) + { + ws_col.set( seg.address(), col ); + } + + short ws_row() + { + return (short) ws_row.get( seg.address() ); + } + + void ws_row( short row ) + { + ws_row.set( seg.address(), row ); + } + } + + // termios structure for termios functions, describing a general terminal interface that is + // provided to control asynchronous communications ports + // @see TERMIOS(3) man-page + static class termios + { + final static GroupLayout LAYOUT; + private final static VarHandle c_iflag; + private final static VarHandle c_oflag; + private final static VarHandle c_cflag; + private final static VarHandle c_lflag; + private final static VarHandle c_ispeed; + private final static VarHandle c_ospeed; + + static + { + LAYOUT = MemoryLayout.structLayout( + ValueLayout.JAVA_LONG.withName( "c_iflag" ), + ValueLayout.JAVA_LONG.withName( "c_oflag" ), + ValueLayout.JAVA_LONG.withName( "c_cflag" ), + ValueLayout.JAVA_LONG.withName( "c_lflag" ), + MemoryLayout.sequenceLayout( 32, ValueLayout.JAVA_BYTE ).withName( "c_cc" ), + ValueLayout.JAVA_LONG.withName( "c_ispeed" ), + ValueLayout.JAVA_LONG.withName( "c_ospeed" ) + ); + c_iflag = LAYOUT.varHandle( MemoryLayout.PathElement.groupElement( "c_iflag" ) ); + c_oflag = LAYOUT.varHandle( MemoryLayout.PathElement.groupElement( "c_oflag" ) ); + c_cflag = LAYOUT.varHandle( MemoryLayout.PathElement.groupElement( "c_cflag" ) ); + c_lflag = LAYOUT.varHandle( MemoryLayout.PathElement.groupElement( "c_lflag" ) ); + c_ispeed = LAYOUT.varHandle( MemoryLayout.PathElement.groupElement( "c_ispeed" ) ); + c_ospeed = LAYOUT.varHandle( MemoryLayout.PathElement.groupElement( "c_ospeed" ) ); + } + + private final MemorySegment seg; + + termios() + { + seg = MemorySegment.allocateNative( LAYOUT, MemorySession.openImplicit() ); + } + + termios( Attributes t ) + { + this(); + // Input flags + long c_iflag = 0; + c_iflag = setFlag( t.getInputFlag( Attributes.InputFlag.IGNBRK ), IGNBRK, c_iflag ); + c_iflag = setFlag( t.getInputFlag( Attributes.InputFlag.BRKINT ), BRKINT, c_iflag ); + c_iflag = setFlag( t.getInputFlag( Attributes.InputFlag.IGNPAR ), IGNPAR, c_iflag ); + c_iflag = setFlag( t.getInputFlag( Attributes.InputFlag.PARMRK ), PARMRK, c_iflag ); + c_iflag = setFlag( t.getInputFlag( Attributes.InputFlag.INPCK ), INPCK, c_iflag ); + c_iflag = setFlag( t.getInputFlag( Attributes.InputFlag.ISTRIP ), ISTRIP, c_iflag ); + c_iflag = setFlag( t.getInputFlag( Attributes.InputFlag.INLCR ), INLCR, c_iflag ); + c_iflag = setFlag( t.getInputFlag( Attributes.InputFlag.IGNCR ), IGNCR, c_iflag ); + c_iflag = setFlag( t.getInputFlag( Attributes.InputFlag.ICRNL ), ICRNL, c_iflag ); + c_iflag = setFlag( t.getInputFlag( Attributes.InputFlag.IXON ), IXON, c_iflag ); + c_iflag = setFlag( t.getInputFlag( Attributes.InputFlag.IXOFF ), IXOFF, c_iflag ); + c_iflag = setFlag( t.getInputFlag( Attributes.InputFlag.IXANY ), IXANY, c_iflag ); + c_iflag = setFlag( t.getInputFlag( Attributes.InputFlag.IMAXBEL ), IMAXBEL, c_iflag ); + c_iflag = setFlag( t.getInputFlag( Attributes.InputFlag.IUTF8 ), IUTF8, c_iflag ); + c_iflag( c_iflag ); + // Output flags + long c_oflag = 0; + c_oflag = setFlag( t.getOutputFlag( Attributes.OutputFlag.OPOST ), OPOST, c_oflag ); + c_oflag = setFlag( t.getOutputFlag( Attributes.OutputFlag.ONLCR ), ONLCR, c_oflag ); + c_oflag = setFlag( t.getOutputFlag( Attributes.OutputFlag.OXTABS ), OXTABS, c_oflag ); + c_oflag = setFlag( t.getOutputFlag( Attributes.OutputFlag.ONOEOT ), ONOEOT, c_oflag ); + c_oflag = setFlag( t.getOutputFlag( Attributes.OutputFlag.OCRNL ), OCRNL, c_oflag ); + c_oflag = setFlag( t.getOutputFlag( Attributes.OutputFlag.ONOCR ), ONOCR, c_oflag ); + c_oflag = setFlag( t.getOutputFlag( Attributes.OutputFlag.ONLRET ), ONLRET, c_oflag ); + c_oflag = setFlag( t.getOutputFlag( Attributes.OutputFlag.OFILL ), OFILL, c_oflag ); + c_oflag = setFlag( t.getOutputFlag( Attributes.OutputFlag.NLDLY ), NLDLY, c_oflag ); + c_oflag = setFlag( t.getOutputFlag( Attributes.OutputFlag.TABDLY ), TABDLY, c_oflag ); + c_oflag = setFlag( t.getOutputFlag( Attributes.OutputFlag.CRDLY ), CRDLY, c_oflag ); + c_oflag = setFlag( t.getOutputFlag( Attributes.OutputFlag.FFDLY ), FFDLY, c_oflag ); + c_oflag = setFlag( t.getOutputFlag( Attributes.OutputFlag.BSDLY ), BSDLY, c_oflag ); + c_oflag = setFlag( t.getOutputFlag( Attributes.OutputFlag.VTDLY ), VTDLY, c_oflag ); + c_oflag = setFlag( t.getOutputFlag( Attributes.OutputFlag.OFDEL ), OFDEL, c_oflag ); + c_oflag( c_oflag ); + // Control flags + long c_cflag = 0; + c_cflag = setFlag( t.getControlFlag( Attributes.ControlFlag.CIGNORE ), CIGNORE, c_cflag ); + c_cflag = setFlag( t.getControlFlag( Attributes.ControlFlag.CS5 ), CS5, c_cflag ); + c_cflag = setFlag( t.getControlFlag( Attributes.ControlFlag.CS6 ), CS6, c_cflag ); + c_cflag = setFlag( t.getControlFlag( Attributes.ControlFlag.CS7 ), CS7, c_cflag ); + c_cflag = setFlag( t.getControlFlag( Attributes.ControlFlag.CS8 ), CS8, c_cflag ); + c_cflag = setFlag( t.getControlFlag( Attributes.ControlFlag.CSTOPB ), CSTOPB, c_cflag ); + c_cflag = setFlag( t.getControlFlag( Attributes.ControlFlag.CREAD ), CREAD, c_cflag ); + c_cflag = setFlag( t.getControlFlag( Attributes.ControlFlag.PARENB ), PARENB, c_cflag ); + c_cflag = setFlag( t.getControlFlag( Attributes.ControlFlag.PARODD ), PARODD, c_cflag ); + c_cflag = setFlag( t.getControlFlag( Attributes.ControlFlag.HUPCL ), HUPCL, c_cflag ); + c_cflag = setFlag( t.getControlFlag( Attributes.ControlFlag.CLOCAL ), CLOCAL, c_cflag ); + c_cflag = setFlag( t.getControlFlag( Attributes.ControlFlag.CCTS_OFLOW ), CCTS_OFLOW, c_cflag ); + c_cflag = setFlag( t.getControlFlag( Attributes.ControlFlag.CRTS_IFLOW ), CRTS_IFLOW, c_cflag ); + c_cflag = setFlag( t.getControlFlag( Attributes.ControlFlag.CDTR_IFLOW ), CDTR_IFLOW, c_cflag ); + c_cflag = setFlag( t.getControlFlag( Attributes.ControlFlag.CDSR_OFLOW ), CDSR_OFLOW, c_cflag ); + c_cflag = setFlag( t.getControlFlag( Attributes.ControlFlag.CCAR_OFLOW ), CCAR_OFLOW, c_cflag ); + c_cflag( c_cflag ); + // Local flags + long c_lflag = 0; + c_lflag = setFlag( t.getLocalFlag( Attributes.LocalFlag.ECHOKE ), ECHOKE, c_lflag ); + c_lflag = setFlag( t.getLocalFlag( Attributes.LocalFlag.ECHOE ), ECHOE, c_lflag ); + c_lflag = setFlag( t.getLocalFlag( Attributes.LocalFlag.ECHOK ), ECHOK, c_lflag ); + c_lflag = setFlag( t.getLocalFlag( Attributes.LocalFlag.ECHO ), ECHO, c_lflag ); + c_lflag = setFlag( t.getLocalFlag( Attributes.LocalFlag.ECHONL ), ECHONL, c_lflag ); + c_lflag = setFlag( t.getLocalFlag( Attributes.LocalFlag.ECHOPRT ), ECHOPRT, c_lflag ); + c_lflag = setFlag( t.getLocalFlag( Attributes.LocalFlag.ECHOCTL ), ECHOCTL, c_lflag ); + c_lflag = setFlag( t.getLocalFlag( Attributes.LocalFlag.ISIG ), ISIG, c_lflag ); + c_lflag = setFlag( t.getLocalFlag( Attributes.LocalFlag.ICANON ), ICANON, c_lflag ); + c_lflag = setFlag( t.getLocalFlag( Attributes.LocalFlag.ALTWERASE ), ALTWERASE, c_lflag ); + c_lflag = setFlag( t.getLocalFlag( Attributes.LocalFlag.IEXTEN ), IEXTEN, c_lflag ); + c_lflag = setFlag( t.getLocalFlag( Attributes.LocalFlag.EXTPROC ), EXTPROC, c_lflag ); + c_lflag = setFlag( t.getLocalFlag( Attributes.LocalFlag.TOSTOP ), TOSTOP, c_lflag ); + c_lflag = setFlag( t.getLocalFlag( Attributes.LocalFlag.FLUSHO ), FLUSHO, c_lflag ); + c_lflag = setFlag( t.getLocalFlag( Attributes.LocalFlag.NOKERNINFO ), NOKERNINFO, c_lflag ); + c_lflag = setFlag( t.getLocalFlag( Attributes.LocalFlag.PENDIN ), PENDIN, c_lflag ); + c_lflag = setFlag( t.getLocalFlag( Attributes.LocalFlag.NOFLSH ), NOFLSH, c_lflag ); + c_lflag( c_lflag ); + // Control chars + byte[] c_cc = new byte[20]; + c_cc[VEOF] = (byte) t.getControlChar( Attributes.ControlChar.VEOF ); + c_cc[VEOL] = (byte) t.getControlChar( Attributes.ControlChar.VEOL ); + c_cc[VEOL2] = (byte) t.getControlChar( Attributes.ControlChar.VEOL2 ); + c_cc[VERASE] = (byte) t.getControlChar( Attributes.ControlChar.VERASE ); + c_cc[VWERASE] = (byte) t.getControlChar( Attributes.ControlChar.VWERASE ); + c_cc[VKILL] = (byte) t.getControlChar( Attributes.ControlChar.VKILL ); + c_cc[VREPRINT] = (byte) t.getControlChar( Attributes.ControlChar.VREPRINT ); + c_cc[VINTR] = (byte) t.getControlChar( Attributes.ControlChar.VINTR ); + c_cc[VQUIT] = (byte) t.getControlChar( Attributes.ControlChar.VQUIT ); + c_cc[VSUSP] = (byte) t.getControlChar( Attributes.ControlChar.VSUSP ); + c_cc[VDSUSP] = (byte) t.getControlChar( Attributes.ControlChar.VDSUSP ); + c_cc[VSTART] = (byte) t.getControlChar( Attributes.ControlChar.VSTART ); + c_cc[VSTOP] = (byte) t.getControlChar( Attributes.ControlChar.VSTOP ); + c_cc[VLNEXT] = (byte) t.getControlChar( Attributes.ControlChar.VLNEXT ); + c_cc[VDISCARD] = (byte) t.getControlChar( Attributes.ControlChar.VDISCARD ); + c_cc[VMIN] = (byte) t.getControlChar( Attributes.ControlChar.VMIN ); + c_cc[VTIME] = (byte) t.getControlChar( Attributes.ControlChar.VTIME ); + c_cc[VSTATUS] = (byte) t.getControlChar( Attributes.ControlChar.VSTATUS ); + c_cc().copyFrom( MemorySegment.ofArray( c_cc ) ); + } + + MemoryAddress address() + { + return seg.address(); + } + + long c_iflag() + { + return (long) c_iflag.get( seg ); + } + + void c_iflag( long f ) + { + c_iflag.set( seg, f ); + } + + long c_oflag() + { + return (long) c_oflag.get( seg ); + } + + void c_oflag( long f ) + { + c_oflag.set( seg, f ); + } + + long c_cflag() + { + return (long) c_cflag.get( seg ); + } + + void c_cflag( long f ) + { + c_cflag.set( seg, f ); + } + + long c_lflag() + { + return (long) c_lflag.get( seg ); + } + + void c_lflag( long f ) + { + c_lflag.set( seg, f ); + } + + MemorySegment c_cc() + { + return seg.asSlice( 32, 20 ); + } + + long c_ispeed() + { + return (long) c_ispeed.get( seg ); + } + + void c_ispeed( long f ) + { + c_ispeed.set( seg, f ); + } + + long c_ospeed() + { + return (long) c_ospeed.get( seg ); + } + + void c_ospeed( long f ) + { + c_ospeed.set( seg, f ); + } + + private static long setFlag( boolean flag, long value, long org ) + { + return flag ? org | value : org; + } + + private static > void addFlag( long value, EnumSet flags, T flag, int v ) + { + if ( ( value & v ) != 0 ) + { + flags.add( flag ); + } + } + + public Attributes asAttributes() + { + Attributes attr = new Attributes(); + // Input flags + long c_iflag = c_iflag(); + EnumSet iflag = attr.getInputFlags(); + addFlag( c_iflag, iflag, Attributes.InputFlag.IGNBRK, IGNBRK ); + addFlag( c_iflag, iflag, Attributes.InputFlag.IGNBRK, IGNBRK ); + addFlag( c_iflag, iflag, Attributes.InputFlag.BRKINT, BRKINT ); + addFlag( c_iflag, iflag, Attributes.InputFlag.IGNPAR, IGNPAR ); + addFlag( c_iflag, iflag, Attributes.InputFlag.PARMRK, PARMRK ); + addFlag( c_iflag, iflag, Attributes.InputFlag.INPCK, INPCK ); + addFlag( c_iflag, iflag, Attributes.InputFlag.ISTRIP, ISTRIP ); + addFlag( c_iflag, iflag, Attributes.InputFlag.INLCR, INLCR ); + addFlag( c_iflag, iflag, Attributes.InputFlag.IGNCR, IGNCR ); + addFlag( c_iflag, iflag, Attributes.InputFlag.ICRNL, ICRNL ); + addFlag( c_iflag, iflag, Attributes.InputFlag.IXON, IXON ); + addFlag( c_iflag, iflag, Attributes.InputFlag.IXOFF, IXOFF ); + addFlag( c_iflag, iflag, Attributes.InputFlag.IXANY, IXANY ); + addFlag( c_iflag, iflag, Attributes.InputFlag.IMAXBEL, IMAXBEL ); + addFlag( c_iflag, iflag, Attributes.InputFlag.IUTF8, IUTF8 ); + // Output flags + long c_oflag = c_oflag(); + EnumSet oflag = attr.getOutputFlags(); + addFlag( c_oflag, oflag, Attributes.OutputFlag.OPOST, OPOST ); + addFlag( c_oflag, oflag, Attributes.OutputFlag.ONLCR, ONLCR ); + addFlag( c_oflag, oflag, Attributes.OutputFlag.OXTABS, OXTABS ); + addFlag( c_oflag, oflag, Attributes.OutputFlag.ONOEOT, ONOEOT ); + addFlag( c_oflag, oflag, Attributes.OutputFlag.OCRNL, OCRNL ); + addFlag( c_oflag, oflag, Attributes.OutputFlag.ONOCR, ONOCR ); + addFlag( c_oflag, oflag, Attributes.OutputFlag.ONLRET, ONLRET ); + addFlag( c_oflag, oflag, Attributes.OutputFlag.OFILL, OFILL ); + addFlag( c_oflag, oflag, Attributes.OutputFlag.NLDLY, NLDLY ); + addFlag( c_oflag, oflag, Attributes.OutputFlag.TABDLY, TABDLY ); + addFlag( c_oflag, oflag, Attributes.OutputFlag.CRDLY, CRDLY ); + addFlag( c_oflag, oflag, Attributes.OutputFlag.FFDLY, FFDLY ); + addFlag( c_oflag, oflag, Attributes.OutputFlag.BSDLY, BSDLY ); + addFlag( c_oflag, oflag, Attributes.OutputFlag.VTDLY, VTDLY ); + addFlag( c_oflag, oflag, Attributes.OutputFlag.OFDEL, OFDEL ); + // Control flags + long c_cflag = c_cflag(); + EnumSet cflag = attr.getControlFlags(); + addFlag( c_cflag, cflag, Attributes.ControlFlag.CIGNORE, CIGNORE ); + addFlag( c_cflag, cflag, Attributes.ControlFlag.CS5, CS5 ); + addFlag( c_cflag, cflag, Attributes.ControlFlag.CS6, CS6 ); + addFlag( c_cflag, cflag, Attributes.ControlFlag.CS7, CS7 ); + addFlag( c_cflag, cflag, Attributes.ControlFlag.CS8, CS8 ); + addFlag( c_cflag, cflag, Attributes.ControlFlag.CSTOPB, CSTOPB ); + addFlag( c_cflag, cflag, Attributes.ControlFlag.CREAD, CREAD ); + addFlag( c_cflag, cflag, Attributes.ControlFlag.PARENB, PARENB ); + addFlag( c_cflag, cflag, Attributes.ControlFlag.PARODD, PARODD ); + addFlag( c_cflag, cflag, Attributes.ControlFlag.HUPCL, HUPCL ); + addFlag( c_cflag, cflag, Attributes.ControlFlag.CLOCAL, CLOCAL ); + addFlag( c_cflag, cflag, Attributes.ControlFlag.CCTS_OFLOW, CCTS_OFLOW ); + addFlag( c_cflag, cflag, Attributes.ControlFlag.CRTS_IFLOW, CRTS_IFLOW ); + addFlag( c_cflag, cflag, Attributes.ControlFlag.CDSR_OFLOW, CDSR_OFLOW ); + addFlag( c_cflag, cflag, Attributes.ControlFlag.CCAR_OFLOW, CCAR_OFLOW ); + // Local flags + long c_lflag = c_lflag(); + EnumSet lflag = attr.getLocalFlags(); + addFlag( c_lflag, lflag, Attributes.LocalFlag.ECHOKE, ECHOKE ); + addFlag( c_lflag, lflag, Attributes.LocalFlag.ECHOE, ECHOE ); + addFlag( c_lflag, lflag, Attributes.LocalFlag.ECHOK, ECHOK ); + addFlag( c_lflag, lflag, Attributes.LocalFlag.ECHO, ECHO ); + addFlag( c_lflag, lflag, Attributes.LocalFlag.ECHONL, ECHONL ); + addFlag( c_lflag, lflag, Attributes.LocalFlag.ECHOPRT, ECHOPRT ); + addFlag( c_lflag, lflag, Attributes.LocalFlag.ECHOCTL, ECHOCTL ); + addFlag( c_lflag, lflag, Attributes.LocalFlag.ISIG, ISIG ); + addFlag( c_lflag, lflag, Attributes.LocalFlag.ICANON, ICANON ); + addFlag( c_lflag, lflag, Attributes.LocalFlag.ALTWERASE, ALTWERASE ); + addFlag( c_lflag, lflag, Attributes.LocalFlag.IEXTEN, IEXTEN ); + addFlag( c_lflag, lflag, Attributes.LocalFlag.EXTPROC, EXTPROC ); + addFlag( c_lflag, lflag, Attributes.LocalFlag.TOSTOP, TOSTOP ); + addFlag( c_lflag, lflag, Attributes.LocalFlag.FLUSHO, FLUSHO ); + addFlag( c_lflag, lflag, Attributes.LocalFlag.NOKERNINFO, NOKERNINFO ); + addFlag( c_lflag, lflag, Attributes.LocalFlag.PENDIN, PENDIN ); + addFlag( c_lflag, lflag, Attributes.LocalFlag.NOFLSH, NOFLSH ); + // Control chars + byte[] c_cc = c_cc().toArray( ValueLayout.JAVA_BYTE ); + EnumMap cc = attr.getControlChars(); + cc.put( Attributes.ControlChar.VEOF, (int) c_cc[VEOF] ); + cc.put( Attributes.ControlChar.VEOL, (int) c_cc[VEOL] ); + cc.put( Attributes.ControlChar.VEOL2, (int) c_cc[VEOL2] ); + cc.put( Attributes.ControlChar.VERASE, (int) c_cc[VERASE] ); + cc.put( Attributes.ControlChar.VWERASE, (int) c_cc[VWERASE] ); + cc.put( Attributes.ControlChar.VKILL, (int) c_cc[VKILL] ); + cc.put( Attributes.ControlChar.VREPRINT, (int) c_cc[VREPRINT] ); + cc.put( Attributes.ControlChar.VINTR, (int) c_cc[VINTR] ); + cc.put( Attributes.ControlChar.VQUIT, (int) c_cc[VQUIT] ); + cc.put( Attributes.ControlChar.VSUSP, (int) c_cc[VSUSP] ); + cc.put( Attributes.ControlChar.VDSUSP, (int) c_cc[VDSUSP] ); + cc.put( Attributes.ControlChar.VSTART, (int) c_cc[VSTART] ); + cc.put( Attributes.ControlChar.VSTOP, (int) c_cc[VSTOP] ); + cc.put( Attributes.ControlChar.VLNEXT, (int) c_cc[VLNEXT] ); + cc.put( Attributes.ControlChar.VDISCARD, (int) c_cc[VDISCARD] ); + cc.put( Attributes.ControlChar.VMIN, (int) c_cc[VMIN] ); + cc.put( Attributes.ControlChar.VTIME, (int) c_cc[VTIME] ); + cc.put( Attributes.ControlChar.VSTATUS, (int) c_cc[VSTATUS] ); + // Return + return attr; + } + } + + static MethodHandle ioctl; + static MethodHandle isatty; + static MethodHandle openpty; + static MethodHandle tcsetattr; + static MethodHandle tcgetattr; + static MethodHandle ttyname_r; + + static + { + // methods + Linker linker = Linker.nativeLinker(); + // https://man7.org/linux/man-pages/man2/ioctl.2.html + ioctl = linker.downcallHandle( + linker.defaultLookup().lookup( "ioctl" ).get(), + FunctionDescriptor.of( ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, + ValueLayout.JAVA_LONG, ValueLayout.ADDRESS ) ); + // https://www.man7.org/linux/man-pages/man3/isatty.3.html + isatty = linker.downcallHandle( + linker.defaultLookup().lookup( "isatty" ).get(), + FunctionDescriptor.of( ValueLayout.JAVA_INT, ValueLayout.JAVA_INT ) ); + // https://man7.org/linux/man-pages/man3/openpty.3.html + openpty = linker.downcallHandle( + linker.defaultLookup().lookup( "openpty" ).get(), + FunctionDescriptor.of( ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, + ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.ADDRESS ) ); + // https://man7.org/linux/man-pages/man3/tcsetattr.3p.html + tcsetattr = linker.downcallHandle( + linker.defaultLookup().lookup( "tcsetattr" ).get(), + FunctionDescriptor.of( ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, + ValueLayout.JAVA_INT, ValueLayout.ADDRESS ) ); + // https://man7.org/linux/man-pages/man3/tcgetattr.3p.html + tcgetattr = linker.downcallHandle( + linker.defaultLookup().lookup( "tcgetattr" ).get(), + FunctionDescriptor.of( ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, + ValueLayout.ADDRESS ) ); + // https://man7.org/linux/man-pages/man3/ttyname.3.html + ttyname_r = linker.downcallHandle( + linker.defaultLookup().lookup( "ttyname_r" ).get(), + FunctionDescriptor.of( ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, ValueLayout.JAVA_LONG ) ); + } + + static Size getTerminalSize( int fd ) + { + try + { + winsize ws = new winsize(); + int res = (int) ioctl.invoke( fd, TIOCGWINSZ, ws.address() ); + return new Size( ws.ws_col(), ws.ws_row() ); + } + catch ( Throwable e ) + { + throw new RuntimeException( "Unable to call ioctl(TIOCGWINSZ)", e ); + } + } + + static void setTerminalSize( int fd, Size size ) + { + try + { + winsize ws = new winsize(); + ws.ws_row( (short) size.getRows() ); + ws.ws_col( (short) size.getColumns() ); + int res = (int) ioctl.invoke( fd, TIOCSWINSZ, ws.address() ); + } + catch ( Throwable e ) + { + throw new RuntimeException( "Unable to call ioctl(TIOCGWINSZ)", e ); + } + } + + static Attributes getAttributes( int fd ) + { + try + { + termios t = new termios(); + int res = (int) tcgetattr.invoke( fd, t.address() ); + return t.asAttributes(); + } + catch ( Throwable e ) + { + throw new RuntimeException( "Unable to call tcgetattr()", e ); + } + } + + static void setAttributes( int fd, Attributes attr ) + { + try + { + termios t = new termios( attr ); + int res = (int) tcsetattr.invoke( fd, TCSANOW, t.address() ); + } + catch ( Throwable e ) + { + throw new RuntimeException( "Unable to call tcsetattr()", e ); + } + } + + static boolean isTty( int fd ) + { + try + { + return (int) isatty.invoke( fd ) == 1; + } + catch ( Throwable e ) + { + throw new RuntimeException( "Unable to call isatty()", e ); + } + } + + static String ttyName( int fd ) + { + try + { + MemorySegment buf = MemorySegment.allocateNative( 64, MemorySession.openImplicit() ); + int res = (int) ttyname_r.invoke( fd, buf, buf.byteSize() ); + byte[] data = buf.toArray( ValueLayout.JAVA_BYTE ); + int len = 0; + while ( data[len] != 0 ) + { + len++; + } + return new String( data, 0, len ); + } + catch ( Throwable e ) + { + throw new RuntimeException( "Unable to call ttyname_r()", e ); + } + } + + static Pty openpty( Attributes attr, Size size ) + { + try + { + winsize ws = new winsize(); + termios t = new termios(); + + int[] master = new int[1]; + int[] slave = new int[1]; + byte[] buf = new byte[64]; + int res = (int) openpty.invoke( master, slave, buf, + attr != null ? new termios( attr ) : null, + size != null ? new winsize( (short) size.getRows(), (short) size.getColumns() ) : null ); + int len = 0; + while ( buf[len] != 0 ) + { + len++; + } + String device = new String( buf, 0, len ); + return new NativePty( master[0], + NativePty.newDescriptor( master[0] ), slave[0], + NativePty.newDescriptor( slave[0] ), device ); + } + catch ( Throwable e ) + { + throw new RuntimeException( "Unable to call openpty()", e ); + } + } + + // CONSTANTS + + private final static int TIOCGWINSZ; + private final static int TIOCSWINSZ; + + private final static int TCSANOW; + private static int TCSADRAIN; + private static int TCSAFLUSH; + + private final static int VEOF; + private final static int VEOL; + private final static int VEOL2; + private final static int VERASE; + private final static int VWERASE; + private final static int VKILL; + private final static int VREPRINT; + private static int VERASE2; + private final static int VINTR; + private final static int VQUIT; + private final static int VSUSP; + private static int VDSUSP; + private final static int VSTART; + private final static int VSTOP; + private final static int VLNEXT; + private final static int VDISCARD; + private final static int VMIN; + private static int VSWTC; + private final static int VTIME; + private static int VSTATUS; + + private final static int IGNBRK; + private final static int BRKINT; + private final static int IGNPAR; + private final static int PARMRK; + private final static int INPCK; + private final static int ISTRIP; + private final static int INLCR; + private final static int IGNCR; + private final static int ICRNL; + private static int IUCLC; + private final static int IXON; + private final static int IXOFF; + private final static int IXANY; + private final static int IMAXBEL; + private static int IUTF8; + + private final static int OPOST; + private static int OLCUC; + private final static int ONLCR; + private static int OXTABS; + private static int NLDLY; + private static int NL0; + private static int NL1; + private final static int TABDLY; + private static int TAB0; + private static int TAB1; + private static int TAB2; + private static int TAB3; + private static int CRDLY; + private static int CR0; + private static int CR1; + private static int CR2; + private static int CR3; + private static int FFDLY; + private static int FF0; + private static int FF1; + private static int XTABS; + private static int BSDLY; + private static int BS0; + private static int BS1; + private static int VTDLY; + private static int VT0; + private static int VT1; + private static int CBAUD; + private static int B0; + private static int B50; + private static int B75; + private static int B110; + private static int B134; + private static int B150; + private static int B200; + private static int B300; + private static int B600; + private static int B1200; + private static int B1800; + private static int B2400; + private static int B4800; + private static int B9600; + private static int B19200; + private static int B38400; + private static int EXTA; + private static int EXTB; + private static int OFDEL; + private static int ONOEOT; + private final static int OCRNL; + private static int ONOCR; + private final static int ONLRET; + private static int OFILL; + + private static int CIGNORE; + private static int CSIZE; + private final static int CS5; + private final static int CS6; + private final static int CS7; + private final static int CS8; + private final static int CSTOPB; + private final static int CREAD; + private final static int PARENB; + private final static int PARODD; + private final static int HUPCL; + private final static int CLOCAL; + private static int CCTS_OFLOW; + private static int CRTS_IFLOW; + private static int CDTR_IFLOW; + private static int CDSR_OFLOW; + private static int CCAR_OFLOW; + + private final static int ECHOKE; + private final static int ECHOE; + private final static int ECHOK; + private final static int ECHO; + private final static int ECHONL; + private final static int ECHOPRT; + private final static int ECHOCTL; + private final static int ISIG; + private final static int ICANON; + private static int XCASE; + private static int ALTWERASE; + private final static int IEXTEN; + private final static int EXTPROC; + private final static int TOSTOP; + private final static int FLUSHO; + private static int NOKERNINFO; + private final static int PENDIN; + private final static int NOFLSH; + + static + { + String osName = System.getProperty( "os.name" ); + if ( osName.startsWith( "Linux" ) ) + { + String arch = System.getProperty( "os.arch" ); + boolean isMipsPpcOrSparc = arch.equals( "mips" ) || arch.equals( "mips64" ) + || arch.equals( "mipsel" ) || arch.equals( "mips64el" ) + || arch.startsWith( "ppc" ) || arch.startsWith( "sparc" ); + TIOCGWINSZ = isMipsPpcOrSparc ? 0x40087468 : 0x00005413; + TIOCSWINSZ = isMipsPpcOrSparc ? 0x80087467 : 0x00005414; + + TCSANOW = 0x0; + TCSADRAIN = 0x1; + TCSAFLUSH = 0x2; + + VINTR = 0; + VQUIT = 1; + VERASE = 2; + VKILL = 3; + VEOF = 4; + VTIME = 5; + VMIN = 6; + VSWTC = 7; + VSTART = 8; + VSTOP = 9; + VSUSP = 10; + VEOL = 11; + VREPRINT = 12; + VDISCARD = 13; + VWERASE = 14; + VLNEXT = 15; + VEOL2 = 16; + + IGNBRK = 0x0000001; + BRKINT = 0x0000002; + IGNPAR = 0x0000004; + PARMRK = 0x0000008; + INPCK = 0x0000010; + ISTRIP = 0x0000020; + INLCR = 0x0000040; + IGNCR = 0x0000080; + ICRNL = 0x0000100; + IUCLC = 0x0000200; + IXON = 0x0000400; + IXANY = 0x0000800; + IXOFF = 0x0001000; + IMAXBEL = 0x0002000; + IUTF8 = 0x0004000; + + OPOST = 0x0000001; + OLCUC = 0x0000002; + ONLCR = 0x0000004; + OCRNL = 0x0000008; + ONOCR = 0x0000010; + ONLRET = 0x0000020; + OFILL = 0x0000040; + OFDEL = 0x0000080; + NLDLY = 0x0000100; + NL0 = 0x0000000; + NL1 = 0x0000100; + CRDLY = 0x0000600; + CR0 = 0x0000000; + CR1 = 0x0000200; + CR2 = 0x0000400; + CR3 = 0x0000600; + TABDLY = 0x0001800; + TAB0 = 0x0000000; + TAB1 = 0x0000800; + TAB2 = 0x0001000; + TAB3 = 0x0001800; + XTABS = 0x0001800; + BSDLY = 0x0002000; + BS0 = 0x0000000; + BS1 = 0x0002000; + VTDLY = 0x0004000; + VT0 = 0x0000000; + VT1 = 0x0004000; + FFDLY = 0x0008000; + FF0 = 0x0000000; + FF1 = 0x0008000; + + CBAUD = 0x000100f; + B0 = 0x0000000; + B50 = 0x0000001; + B75 = 0x0000002; + B110 = 0x0000003; + B134 = 0x0000004; + B150 = 0x0000005; + B200 = 0x0000006; + B300 = 0x0000007; + B600 = 0x0000008; + B1200 = 0x0000009; + B1800 = 0x000000a; + B2400 = 0x000000b; + B4800 = 0x000000c; + B9600 = 0x000000d; + B19200 = 0x000000e; + B38400 = 0x000000f; + EXTA = B19200; + EXTB = B38400; + CSIZE = 0x0000030; + CS5 = 0x0000000; + CS6 = 0x0000010; + CS7 = 0x0000020; + CS8 = 0x0000030; + CSTOPB = 0x0000040; + CREAD = 0x0000080; + PARENB = 0x0000100; + PARODD = 0x0000200; + HUPCL = 0x0000400; + CLOCAL = 0x0000800; + + ISIG = 0x0000001; + ICANON = 0x0000002; + XCASE = 0x0000004; + ECHO = 0x0000008; + ECHOE = 0x0000010; + ECHOK = 0x0000020; + ECHONL = 0x0000040; + NOFLSH = 0x0000080; + TOSTOP = 0x0000100; + ECHOCTL = 0x0000200; + ECHOPRT = 0x0000400; + ECHOKE = 0x0000800; + FLUSHO = 0x0001000; + PENDIN = 0x0002000; + IEXTEN = 0x0008000; + EXTPROC = 0x0010000; + } + else if ( osName.startsWith( "Solaris" ) || osName.startsWith( "SunOS" ) ) + { + int _TIOC = ( 'T' << 8 ); + TIOCGWINSZ = ( _TIOC | 104 ); + TIOCSWINSZ = ( _TIOC | 103 ); + + TCSANOW = 0x0; + TCSADRAIN = 0x1; + TCSAFLUSH = 0x2; + + VINTR = 0; + VQUIT = 1; + VERASE = 2; + VKILL = 3; + VEOF = 4; + VTIME = 5; + VMIN = 6; + VSWTC = 7; + VSTART = 8; + VSTOP = 9; + VSUSP = 10; + VEOL = 11; + VREPRINT = 12; + VDISCARD = 13; + VWERASE = 14; + VLNEXT = 15; + VEOL2 = 16; + + IGNBRK = 0x0000001; + BRKINT = 0x0000002; + IGNPAR = 0x0000004; + PARMRK = 0x0000010; + INPCK = 0x0000020; + ISTRIP = 0x0000040; + INLCR = 0x0000100; + IGNCR = 0x0000200; + ICRNL = 0x0000400; + IUCLC = 0x0001000; + IXON = 0x0002000; + IXANY = 0x0004000; + IXOFF = 0x0010000; + IMAXBEL = 0x0020000; + IUTF8 = 0x0040000; + + OPOST = 0x0000001; + OLCUC = 0x0000002; + ONLCR = 0x0000004; + OCRNL = 0x0000010; + ONOCR = 0x0000020; + ONLRET = 0x0000040; + OFILL = 0x0000100; + OFDEL = 0x0000200; + NLDLY = 0x0000400; + NL0 = 0x0000000; + NL1 = 0x0000400; + CRDLY = 0x0003000; + CR0 = 0x0000000; + CR1 = 0x0001000; + CR2 = 0x0002000; + CR3 = 0x0003000; + TABDLY = 0x0014000; + TAB0 = 0x0000000; + TAB1 = 0x0004000; + TAB2 = 0x0010000; + TAB3 = 0x0014000; + XTABS = 0x0014000; + BSDLY = 0x0020000; + BS0 = 0x0000000; + BS1 = 0x0020000; + VTDLY = 0x0040000; + VT0 = 0x0000000; + VT1 = 0x0040000; + FFDLY = 0x0100000; + FF0 = 0x0000000; + FF1 = 0x0100000; + + CBAUD = 0x0010017; + B0 = 0x0000000; + B50 = 0x0000001; + B75 = 0x0000002; + B110 = 0x0000003; + B134 = 0x0000004; + B150 = 0x0000005; + B200 = 0x0000006; + B300 = 0x0000007; + B600 = 0x0000010; + B1200 = 0x0000011; + B1800 = 0x0000012; + B2400 = 0x0000013; + B4800 = 0x0000014; + B9600 = 0x0000015; + B19200 = 0x0000016; + B38400 = 0x0000017; + EXTA = 0xB19200; + EXTB = 0xB38400; + CSIZE = 0x0000060; + CS5 = 0x0000000; + CS6 = 0x0000020; + CS7 = 0x0000040; + CS8 = 0x0000060; + CSTOPB = 0x0000100; + CREAD = 0x0000200; + PARENB = 0x0000400; + PARODD = 0x0001000; + HUPCL = 0x0002000; + CLOCAL = 0x0004000; + + ISIG = 0x0000001; + ICANON = 0x0000002; + XCASE = 0x0000004; + ECHO = 0x0000010; + ECHOE = 0x0000020; + ECHOK = 0x0000040; + ECHONL = 0x0000100; + NOFLSH = 0x0000200; + TOSTOP = 0x0000400; + ECHOCTL = 0x0001000; + ECHOPRT = 0x0002000; + ECHOKE = 0x0004000; + FLUSHO = 0x0010000; + PENDIN = 0x0040000; + IEXTEN = 0x0100000; + EXTPROC = 0x0200000; + } + else if ( osName.startsWith( "Mac" ) || osName.startsWith( "Darwin" ) ) + { + TIOCGWINSZ = 0x40087468; + TIOCSWINSZ = 0x80087467; + + TCSANOW = 0x00000000; + + VEOF = 0; + VEOL = 1; + VEOL2 = 2; + VERASE = 3; + VWERASE = 4; + VKILL = 5; + VREPRINT = 6; + VINTR = 8; + VQUIT = 9; + VSUSP = 10; + VDSUSP = 11; + VSTART = 12; + VSTOP = 13; + VLNEXT = 14; + VDISCARD = 15; + VMIN = 16; + VTIME = 17; + VSTATUS = 18; + + IGNBRK = 0x00000001; + BRKINT = 0x00000002; + IGNPAR = 0x00000004; + PARMRK = 0x00000008; + INPCK = 0x00000010; + ISTRIP = 0x00000020; + INLCR = 0x00000040; + IGNCR = 0x00000080; + ICRNL = 0x00000100; + IXON = 0x00000200; + IXOFF = 0x00000400; + IXANY = 0x00000800; + IMAXBEL = 0x00002000; + IUTF8 = 0x00004000; + + OPOST = 0x00000001; + ONLCR = 0x00000002; + OXTABS = 0x00000004; + ONOEOT = 0x00000008; + OCRNL = 0x00000010; + ONOCR = 0x00000020; + ONLRET = 0x00000040; + OFILL = 0x00000080; + NLDLY = 0x00000300; + TABDLY = 0x00000c04; + CRDLY = 0x00003000; + FFDLY = 0x00004000; + BSDLY = 0x00008000; + VTDLY = 0x00010000; + OFDEL = 0x00020000; + + CIGNORE = 0x00000001; + CS5 = 0x00000000; + CS6 = 0x00000100; + CS7 = 0x00000200; + CS8 = 0x00000300; + CSTOPB = 0x00000400; + CREAD = 0x00000800; + PARENB = 0x00001000; + PARODD = 0x00002000; + HUPCL = 0x00004000; + CLOCAL = 0x00008000; + CCTS_OFLOW = 0x00010000; + CRTS_IFLOW = 0x00020000; + CDTR_IFLOW = 0x00040000; + CDSR_OFLOW = 0x00080000; + CCAR_OFLOW = 0x00100000; + + ECHOKE = 0x00000001; + ECHOE = 0x00000002; + ECHOK = 0x00000004; + ECHO = 0x00000008; + ECHONL = 0x00000010; + ECHOPRT = 0x00000020; + ECHOCTL = 0x00000040; + ISIG = 0x00000080; + ICANON = 0x00000100; + ALTWERASE = 0x00000200; + IEXTEN = 0x00000400; + EXTPROC = 0x00000800; + TOSTOP = 0x00400000; + FLUSHO = 0x00800000; + NOKERNINFO = 0x02000000; + PENDIN = 0x20000000; + NOFLSH = 0x80000000; + } + else if ( osName.startsWith( "FreeBSD" ) ) + { + TIOCGWINSZ = 0x40087468; + TIOCSWINSZ = 0x80087467; + + TCSANOW = 0x0; + TCSADRAIN = 0x1; + TCSAFLUSH = 0x2; + + VEOF = 0; + VEOL = 1; + VEOL2 = 2; + VERASE = 3; + VWERASE = 4; + VKILL = 5; + VREPRINT = 6; + VERASE2 = 7; + VINTR = 8; + VQUIT = 9; + VSUSP = 10; + VDSUSP = 11; + VSTART = 12; + VSTOP = 13; + VLNEXT = 14; + VDISCARD = 15; + VMIN = 16; + VTIME = 17; + VSTATUS = 18; + + IGNBRK = 0x0000001; + BRKINT = 0x0000002; + IGNPAR = 0x0000004; + PARMRK = 0x0000008; + INPCK = 0x0000010; + ISTRIP = 0x0000020; + INLCR = 0x0000040; + IGNCR = 0x0000080; + ICRNL = 0x0000100; + IXON = 0x0000200; + IXOFF = 0x0000400; + IXANY = 0x0000800; + IMAXBEL = 0x0002000; + + OPOST = 0x0000001; + ONLCR = 0x0000002; + TABDLY = 0x0000004; + TAB0 = 0x0000000; + TAB3 = 0x0000004; + ONOEOT = 0x0000008; + OCRNL = 0x0000010; + ONLRET = 0x0000040; + + CIGNORE = 0x0000001; + CSIZE = 0x0000300; + CS5 = 0x0000000; + CS6 = 0x0000100; + CS7 = 0x0000200; + CS8 = 0x0000300; + CSTOPB = 0x0000400; + CREAD = 0x0000800; + PARENB = 0x0001000; + PARODD = 0x0002000; + HUPCL = 0x0004000; + CLOCAL = 0x0008000; + + ECHOKE = 0x0000001; + ECHOE = 0x0000002; + ECHOK = 0x0000004; + ECHO = 0x0000008; + ECHONL = 0x0000010; + ECHOPRT = 0x0000020; + ECHOCTL = 0x0000040; + ISIG = 0x0000080; + ICANON = 0x0000100; + ALTWERASE = 0x000200; + IEXTEN = 0x0000400; + EXTPROC = 0x0000800; + TOSTOP = 0x0400000; + FLUSHO = 0x0800000; + PENDIN = 0x2000000; + NOFLSH = 0x8000000; + } + else + { + throw new UnsupportedOperationException(); + } + } +} diff --git a/terminal/src/main/java/org/jline/terminal/impl/jep424/Jep424TerminalProvider.java b/terminal/src/main/java/org/jline/terminal/impl/jep424/Jep424TerminalProvider.java index 80573b87f..843f3aac2 100644 --- a/terminal/src/main/java/org/jline/terminal/impl/jep424/Jep424TerminalProvider.java +++ b/terminal/src/main/java/org/jline/terminal/impl/jep424/Jep424TerminalProvider.java @@ -16,30 +16,14 @@ package org.jline.terminal.impl.jep424; import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.lang.foreign.FunctionDescriptor; -import java.lang.foreign.GroupLayout; -import java.lang.foreign.Linker; -import java.lang.foreign.MemoryAddress; -import java.lang.foreign.MemoryLayout; -import java.lang.foreign.MemorySegment; -import java.lang.foreign.MemorySession; -import java.lang.foreign.ValueLayout; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.VarHandle; -import java.lang.reflect.Constructor; import java.nio.charset.Charset; -import java.util.EnumMap; -import java.util.EnumSet; import org.jline.terminal.Attributes; import org.jline.terminal.Size; import org.jline.terminal.Terminal; -import org.jline.terminal.impl.AbstractPty; import org.jline.terminal.impl.PosixPtyTerminal; import org.jline.terminal.impl.PosixSysTerminal; import org.jline.terminal.spi.Pty; @@ -56,13 +40,12 @@ public String name() } @Override - public Terminal sysTerminal(String name, String type, boolean ansiPassThrough, Charset encoding, + public Terminal sysTerminal(String name, String type, boolean ansiPassThrough, Charset encoding, boolean nativeSignals, Terminal.SignalHandler signalHandler, boolean paused, Stream consoleStream) throws IOException { if ( OSUtils.IS_WINDOWS) { -// return NativeWinSysTerminal.createTerminal(name, type, ansiPassThrough, encoding, -// nativeSignals, signalHandler, paused, consoleStream); - throw new UnsupportedOperationException(); + return NativeWinSysTerminal.createTerminal(name, type, ansiPassThrough, encoding, + nativeSignals, signalHandler, paused, consoleStream); } else { Pty pty = new NativePty(-1, null, 0, FileDescriptor.in, consoleStream == Stream.Output ? 1 : 2, @@ -91,8 +74,7 @@ public boolean isSystemStream(Stream stream) { } public boolean isWindowsSystemStream(Stream stream) { - //return NativeWinSysTerminal.isWindowsSystemStream(stream); - throw new UnsupportedOperationException(); + return NativeWinSysTerminal.isWindowsSystemStream(stream); } public boolean isPosixSystemStream(Stream stream) { @@ -104,1442 +86,5 @@ public String systemStreamName(Stream stream) { return NativePty.posixSystemStreamName(stream); } - static class NativePty extends AbstractPty - { - private final int master; - private final int slave; - private final int slaveOut; - private final String name; - private final FileDescriptor masterFD; - private final FileDescriptor slaveFD; - private final FileDescriptor slaveOutFD; - - public NativePty(int master, FileDescriptor masterFD, int slave, FileDescriptor slaveFD, String name) { - this(master, masterFD, slave, slaveFD, slave, slaveFD, name); - } - - public NativePty(int master, FileDescriptor masterFD, int slave, FileDescriptor slaveFD, int slaveOut, FileDescriptor slaveOutFD, String name) { - this.master = master; - this.slave = slave; - this.slaveOut = slaveOut; - this.name = name; - this.masterFD = masterFD; - this.slaveFD = slaveFD; - this.slaveOutFD = slaveOutFD; - } - - @Override - public void close() throws IOException { - if (master > 0) { - getMasterInput().close(); - } - if (slave > 0) { - getSlaveInput().close(); - } - } - - public int getMaster() { - return master; - } - - public int getSlave() { - return slave; - } - - public int getSlaveOut() { - return slaveOut; - } - - public String getName() { - return name; - } - - public FileDescriptor getMasterFD() { - return masterFD; - } - - public FileDescriptor getSlaveFD() { - return slaveFD; - } - - public FileDescriptor getSlaveOutFD() { - return slaveOutFD; - } - - public InputStream getMasterInput() { - return new FileInputStream(getMasterFD()); - } - - public OutputStream getMasterOutput() { - return new FileOutputStream(getMasterFD()); - } - - protected InputStream doGetSlaveInput() { - return new FileInputStream(getSlaveFD()); - } - - public OutputStream getSlaveOutput() { - return new FileOutputStream(getSlaveOutFD()); - } - - @Override - public Attributes getAttr() throws IOException { - return CLibrary.getAttributes(slave); - } - - @Override - protected void doSetAttr(Attributes attr) throws IOException { - CLibrary.setAttributes(slave, attr); - } - - @Override - public Size getSize() throws IOException { - return CLibrary.getTerminalSize(slave); - } - - @Override - public void setSize(Size size) throws IOException { - CLibrary.setTerminalSize(slave, size); - } - - @Override - public String toString() { - return "NativePty[" + getName() + "]"; - } - - protected static FileDescriptor newDescriptor(int fd) { - try { - Constructor cns = FileDescriptor.class.getDeclaredConstructor(int.class); - cns.setAccessible(true); - return cns.newInstance(fd); - } catch (Throwable e) { - throw new RuntimeException("Unable to create FileDescriptor", e); - } - } - - public static boolean isPosixSystemStream(TerminalProvider.Stream stream) { - return switch ( stream ) { - case Input -> CLibrary.isTty( 0 ); - case Output -> CLibrary.isTty( 1 ); - case Error -> CLibrary.isTty( 2 ); - }; - } - - public static String posixSystemStreamName(TerminalProvider.Stream stream) { - return switch ( stream ) { - case Input -> CLibrary.ttyName( 0 ); - case Output -> CLibrary.ttyName( 1 ); - case Error -> CLibrary.ttyName( 2 ); - }; - } - } - - /* - public static class NativeWinSysTerminal extends AbstractWindowsTerminal - { - - private static final long consoleIn = Kernel32.GetStdHandle( STD_INPUT_HANDLE ); - private static final long consoleOut = Kernel32.GetStdHandle( STD_OUTPUT_HANDLE ); - private static final long consoleErr = Kernel32.GetStdHandle( STD_ERROR_HANDLE ); - - public static NativeWinSysTerminal createTerminal( String name, String type, boolean ansiPassThrough, - Charset encoding, - boolean nativeSignals, SignalHandler signalHandler, - boolean paused, - TerminalProvider.Stream consoleStream ) throws IOException - { - Writer writer; - int[] mode = new int[1]; - long console; - switch ( consoleStream ) - { - case Output: - console = consoleOut; - break; - case Error: - console = consoleErr; - break; - default: - throw new IllegalArgumentException( "Unsupport stream for console: " + consoleStream ); - } - if ( ansiPassThrough ) - { - if ( type == null ) - { - type = OSUtils.IS_CONEMU ? TYPE_WINDOWS_CONEMU : TYPE_WINDOWS; - } - writer = new JansiWinConsoleWriter(); - } - else - { - if ( Kernel32.GetConsoleMode( console, mode ) == 0 ) - { - throw new IOException( "Failed to get console mode: " + getLastErrorMessage() ); - } - if ( Kernel32.SetConsoleMode( console, - mode[0] | AbstractWindowsTerminal.ENABLE_VIRTUAL_TERMINAL_PROCESSING ) != 0 ) - { - if ( type == null ) - { - type = TYPE_WINDOWS_VTP; - } - writer = new JansiWinConsoleWriter(); - } - else if ( OSUtils.IS_CONEMU ) - { - if ( type == null ) - { - type = TYPE_WINDOWS_CONEMU; - } - writer = new JansiWinConsoleWriter(); - } - else - { - if ( type == null ) - { - type = TYPE_WINDOWS; - } - writer = new WindowsAnsiWriter( new BufferedWriter( new JansiWinConsoleWriter() ) ); - } - } - if ( Kernel32.GetConsoleMode( consoleIn, mode ) == 0 ) - { - throw new IOException( "Failed to get console mode: " + getLastErrorMessage() ); - } - WinSysTerminal terminal = new WinSysTerminal( writer, name, type, encoding, nativeSignals, - signalHandler, consoleIn, console ); - // Start input pump thread - if ( !paused ) - { - terminal.resume(); - } - return terminal; - } - - public static boolean isWindowsSystemStream( JansiSupport.Stream 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; - } - - private long console; - private long outputHandle; - - WinSysTerminal( Writer writer, String name, String type, Charset encoding, boolean nativeSignals, - SignalHandler signalHandler, - long console, long outputHandle ) throws IOException - { - super( writer, name, type, encoding, nativeSignals, signalHandler ); - this.console = console; - this.outputHandle = outputHandle; - } - - @Override - protected int getConsoleMode() - { - int[] mode = new int[1]; - if ( Kernel32.GetConsoleMode( console, mode ) == 0 ) - { - return -1; - } - return mode[0]; - } - - @Override - protected void setConsoleMode( int mode ) - { - Kernel32.SetConsoleMode( console, mode ); - } - - public Size getSize() - { - Kernel32.CONSOLE_SCREEN_BUFFER_INFO info = new Kernel32.CONSOLE_SCREEN_BUFFER_INFO(); - Kernel32.GetConsoleScreenBufferInfo( outputHandle, info ); - return new Size( info.windowWidth(), info.windowHeight() ); - } - - @Override - public Size getBufferSize() - { - Kernel32.CONSOLE_SCREEN_BUFFER_INFO info = new Kernel32.CONSOLE_SCREEN_BUFFER_INFO(); - Kernel32.GetConsoleScreenBufferInfo( outputHandle, info ); - return new Size( info.size.x, info.size.y ); - } - - protected boolean processConsoleInput() throws IOException - { - Kernel32.INPUT_RECORD[] events; - if ( console != Kernel32.INVALID_HANDLE_VALUE - && Kernel32.WaitForSingleObject( console, 100 ) == 0 ) - { - events = readConsoleInputHelper( console, 1, false ); - } - else - { - return false; - } - - boolean flush = false; - for ( Kernel32.INPUT_RECORD event : events ) - { - if ( event.eventType == Kernel32.INPUT_RECORD.KEY_EVENT ) - { - Kernel32.KEY_EVENT_RECORD keyEvent = event.keyEvent; - processKeyEvent( keyEvent.keyDown, keyEvent.keyCode, keyEvent.uchar, keyEvent.controlKeyState ); - flush = true; - } - else if ( event.eventType == Kernel32.INPUT_RECORD.WINDOW_BUFFER_SIZE_EVENT ) - { - raise( Signal.WINCH ); - } - else if ( event.eventType == Kernel32.INPUT_RECORD.MOUSE_EVENT ) - { - processMouseEvent( event.mouseEvent ); - flush = true; - } - else if ( event.eventType == Kernel32.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( outputHandle, 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(); - } - } - */ - - static class CLibrary - { - // Window sizes. - // @see IOCTL_TTY(2) man-page - static class winsize - { - static GroupLayout layout; - static VarHandle ws_col; - static VarHandle ws_row; - static { - layout = MemoryLayout.structLayout( - ValueLayout.JAVA_SHORT.withName("ws_row"), - ValueLayout.JAVA_SHORT.withName("ws_col"), - ValueLayout.JAVA_SHORT, - ValueLayout.JAVA_SHORT - ); - ws_row = layout.varHandle(MemoryLayout.PathElement.groupElement("ws_row")); - ws_col = layout.varHandle(MemoryLayout.PathElement.groupElement("ws_col")); - } - private final MemorySegment seg; - winsize() { - seg = MemorySegment.allocateNative( layout, MemorySession.openImplicit()); - } - winsize(short ws_col, short ws_row) { - this(); - ws_col(ws_col); - ws_row(ws_row); - } - MemoryAddress address() { - return seg.address(); - } - short ws_col() { - return (short) ws_col.get(seg.address()); - } - void ws_col(short col) { - ws_col.set(seg.address(), col); - } - short ws_row() { - return (short) ws_row.get(seg.address()); - } - void ws_row(short row) { - ws_row.set(seg.address(), row); - } - } - // termios structure for termios functions, describing a general terminal interface that is - // provided to control asynchronous communications ports - // @see TERMIOS(3) man-page - static class termios { - static GroupLayout layout; - static VarHandle c_iflag; - static VarHandle c_oflag; - static VarHandle c_cflag; - static VarHandle c_lflag; - static VarHandle c_ispeed; - static VarHandle c_ospeed; - static { - layout = MemoryLayout.structLayout( - ValueLayout.JAVA_LONG.withName("c_iflag"), - ValueLayout.JAVA_LONG.withName("c_oflag"), - ValueLayout.JAVA_LONG.withName("c_cflag"), - ValueLayout.JAVA_LONG.withName("c_lflag"), - MemoryLayout.sequenceLayout(32, ValueLayout.JAVA_BYTE).withName( "c_cc" ), - ValueLayout.JAVA_LONG.withName("c_ispeed"), - ValueLayout.JAVA_LONG.withName("c_ospeed") - ); - c_iflag = layout.varHandle(MemoryLayout.PathElement.groupElement("c_iflag")); - c_oflag = layout.varHandle(MemoryLayout.PathElement.groupElement("c_oflag")); - c_cflag = layout.varHandle(MemoryLayout.PathElement.groupElement("c_cflag")); - c_lflag = layout.varHandle(MemoryLayout.PathElement.groupElement("c_lflag")); - c_ispeed = layout.varHandle(MemoryLayout.PathElement.groupElement("c_ispeed")); - c_ospeed = layout.varHandle(MemoryLayout.PathElement.groupElement("c_ospeed")); - } - private final MemorySegment seg; - termios() { - seg = MemorySegment.allocateNative(layout, MemorySession.openImplicit()); - } - termios(Attributes t) { - this(); - // Input flags - long c_iflag = 0; - c_iflag = setFlag(t.getInputFlag(Attributes.InputFlag.IGNBRK), IGNBRK, c_iflag); - c_iflag = setFlag(t.getInputFlag(Attributes.InputFlag.BRKINT), BRKINT, c_iflag); - c_iflag = setFlag(t.getInputFlag(Attributes.InputFlag.IGNPAR), IGNPAR, c_iflag); - c_iflag = setFlag(t.getInputFlag(Attributes.InputFlag.PARMRK), PARMRK, c_iflag); - c_iflag = setFlag(t.getInputFlag(Attributes.InputFlag.INPCK), INPCK, c_iflag); - c_iflag = setFlag(t.getInputFlag(Attributes.InputFlag.ISTRIP), ISTRIP, c_iflag); - c_iflag = setFlag(t.getInputFlag(Attributes.InputFlag.INLCR), INLCR, c_iflag); - c_iflag = setFlag(t.getInputFlag(Attributes.InputFlag.IGNCR), IGNCR, c_iflag); - c_iflag = setFlag(t.getInputFlag(Attributes.InputFlag.ICRNL), ICRNL, c_iflag); - c_iflag = setFlag(t.getInputFlag(Attributes.InputFlag.IXON), IXON, c_iflag); - c_iflag = setFlag(t.getInputFlag(Attributes.InputFlag.IXOFF), IXOFF, c_iflag); - c_iflag = setFlag(t.getInputFlag(Attributes.InputFlag.IXANY), IXANY, c_iflag); - c_iflag = setFlag(t.getInputFlag(Attributes.InputFlag.IMAXBEL), IMAXBEL, c_iflag); - c_iflag = setFlag(t.getInputFlag(Attributes.InputFlag.IUTF8), IUTF8, c_iflag); - c_iflag(c_iflag); - // Output flags - long c_oflag = 0; - c_oflag = setFlag(t.getOutputFlag(Attributes.OutputFlag.OPOST), OPOST, c_oflag); - c_oflag = setFlag(t.getOutputFlag(Attributes.OutputFlag.ONLCR), ONLCR, c_oflag); - c_oflag = setFlag(t.getOutputFlag(Attributes.OutputFlag.OXTABS), OXTABS, c_oflag); - c_oflag = setFlag(t.getOutputFlag(Attributes.OutputFlag.ONOEOT), ONOEOT, c_oflag); - c_oflag = setFlag(t.getOutputFlag(Attributes.OutputFlag.OCRNL), OCRNL, c_oflag); - c_oflag = setFlag(t.getOutputFlag(Attributes.OutputFlag.ONOCR), ONOCR, c_oflag); - c_oflag = setFlag(t.getOutputFlag(Attributes.OutputFlag.ONLRET), ONLRET, c_oflag); - c_oflag = setFlag(t.getOutputFlag(Attributes.OutputFlag.OFILL), OFILL, c_oflag); - c_oflag = setFlag(t.getOutputFlag(Attributes.OutputFlag.NLDLY), NLDLY, c_oflag); - c_oflag = setFlag(t.getOutputFlag(Attributes.OutputFlag.TABDLY), TABDLY, c_oflag); - c_oflag = setFlag(t.getOutputFlag(Attributes.OutputFlag.CRDLY), CRDLY, c_oflag); - c_oflag = setFlag(t.getOutputFlag(Attributes.OutputFlag.FFDLY), FFDLY, c_oflag); - c_oflag = setFlag(t.getOutputFlag(Attributes.OutputFlag.BSDLY), BSDLY, c_oflag); - c_oflag = setFlag(t.getOutputFlag(Attributes.OutputFlag.VTDLY), VTDLY, c_oflag); - c_oflag = setFlag(t.getOutputFlag(Attributes.OutputFlag.OFDEL), OFDEL, c_oflag); - c_oflag(c_oflag); - // Control flags - long c_cflag = 0; - c_cflag = setFlag(t.getControlFlag(Attributes.ControlFlag.CIGNORE), CIGNORE, c_cflag); - c_cflag = setFlag(t.getControlFlag(Attributes.ControlFlag.CS5), CS5, c_cflag); - c_cflag = setFlag(t.getControlFlag(Attributes.ControlFlag.CS6), CS6, c_cflag); - c_cflag = setFlag(t.getControlFlag(Attributes.ControlFlag.CS7), CS7, c_cflag); - c_cflag = setFlag(t.getControlFlag(Attributes.ControlFlag.CS8), CS8, c_cflag); - c_cflag = setFlag(t.getControlFlag(Attributes.ControlFlag.CSTOPB), CSTOPB, c_cflag); - c_cflag = setFlag(t.getControlFlag(Attributes.ControlFlag.CREAD), CREAD, c_cflag); - c_cflag = setFlag(t.getControlFlag(Attributes.ControlFlag.PARENB), PARENB, c_cflag); - c_cflag = setFlag(t.getControlFlag(Attributes.ControlFlag.PARODD), PARODD, c_cflag); - c_cflag = setFlag(t.getControlFlag(Attributes.ControlFlag.HUPCL), HUPCL, c_cflag); - c_cflag = setFlag(t.getControlFlag(Attributes.ControlFlag.CLOCAL), CLOCAL, c_cflag); - c_cflag = setFlag(t.getControlFlag(Attributes.ControlFlag.CCTS_OFLOW), CCTS_OFLOW, c_cflag); - c_cflag = setFlag(t.getControlFlag(Attributes.ControlFlag.CRTS_IFLOW), CRTS_IFLOW, c_cflag); - c_cflag = setFlag(t.getControlFlag(Attributes.ControlFlag.CDTR_IFLOW), CDTR_IFLOW, c_cflag); - c_cflag = setFlag(t.getControlFlag(Attributes.ControlFlag.CDSR_OFLOW), CDSR_OFLOW, c_cflag); - c_cflag = setFlag(t.getControlFlag(Attributes.ControlFlag.CCAR_OFLOW), CCAR_OFLOW, c_cflag); - c_cflag(c_cflag); - // Local flags - long c_lflag = 0; - c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.ECHOKE), ECHOKE, c_lflag); - c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.ECHOE), ECHOE, c_lflag); - c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.ECHOK), ECHOK, c_lflag); - c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.ECHO), ECHO, c_lflag); - c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.ECHONL), ECHONL, c_lflag); - c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.ECHOPRT), ECHOPRT, c_lflag); - c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.ECHOCTL), ECHOCTL, c_lflag); - c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.ISIG), ISIG, c_lflag); - c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.ICANON), ICANON, c_lflag); - c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.ALTWERASE), ALTWERASE, c_lflag); - c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.IEXTEN), IEXTEN, c_lflag); - c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.EXTPROC), EXTPROC, c_lflag); - c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.TOSTOP), TOSTOP, c_lflag); - c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.FLUSHO), FLUSHO, c_lflag); - c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.NOKERNINFO), NOKERNINFO, c_lflag); - c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.PENDIN), PENDIN, c_lflag); - c_lflag = setFlag(t.getLocalFlag(Attributes.LocalFlag.NOFLSH), NOFLSH, c_lflag); - c_lflag(c_lflag); - // Control chars - byte[] c_cc = new byte[20]; - c_cc[VEOF] = (byte) t.getControlChar(Attributes.ControlChar.VEOF); - c_cc[VEOL] = (byte) t.getControlChar(Attributes.ControlChar.VEOL); - c_cc[VEOL2] = (byte) t.getControlChar(Attributes.ControlChar.VEOL2); - c_cc[VERASE] = (byte) t.getControlChar(Attributes.ControlChar.VERASE); - c_cc[VWERASE] = (byte) t.getControlChar(Attributes.ControlChar.VWERASE); - c_cc[VKILL] = (byte) t.getControlChar(Attributes.ControlChar.VKILL); - c_cc[VREPRINT] = (byte) t.getControlChar(Attributes.ControlChar.VREPRINT); - c_cc[VINTR] = (byte) t.getControlChar(Attributes.ControlChar.VINTR); - c_cc[VQUIT] = (byte) t.getControlChar(Attributes.ControlChar.VQUIT); - c_cc[VSUSP] = (byte) t.getControlChar(Attributes.ControlChar.VSUSP); - c_cc[VDSUSP] = (byte) t.getControlChar(Attributes.ControlChar.VDSUSP); - c_cc[VSTART] = (byte) t.getControlChar(Attributes.ControlChar.VSTART); - c_cc[VSTOP] = (byte) t.getControlChar(Attributes.ControlChar.VSTOP); - c_cc[VLNEXT] = (byte) t.getControlChar(Attributes.ControlChar.VLNEXT); - c_cc[VDISCARD] = (byte) t.getControlChar(Attributes.ControlChar.VDISCARD); - c_cc[VMIN] = (byte) t.getControlChar(Attributes.ControlChar.VMIN); - c_cc[VTIME] = (byte) t.getControlChar(Attributes.ControlChar.VTIME); - c_cc[VSTATUS] = (byte) t.getControlChar(Attributes.ControlChar.VSTATUS); - c_cc().copyFrom(MemorySegment.ofArray(c_cc)); - } - MemoryAddress address() { - return seg.address(); - } - long c_iflag() { - return (long) c_iflag.get(seg); - } - void c_iflag(long f) { - c_iflag.set(seg, f); - } - long c_oflag() { - return (long) c_oflag.get(seg); - } - void c_oflag(long f) { - c_oflag.set(seg, f); - } - long c_cflag() { - return (long) c_cflag.get(seg); - } - void c_cflag(long f) { - c_cflag.set(seg, f); - } - long c_lflag() { - return (long) c_lflag.get(seg); - } - void c_lflag(long f) { - c_lflag.set(seg, f); - } - MemorySegment c_cc() { - return seg.asSlice(32, 20); - } - long c_ispeed() { - return (long) c_ispeed.get(seg); - } - void c_ispeed(long f) { - c_ispeed.set(seg, f); - } - long c_ospeed() { - return (long) c_ospeed.get(seg); - } - void c_ospeed(long f) { - c_ospeed.set(seg, f); - } - - private static long setFlag(boolean flag, long value, long org) { - return flag ? org | value : org; - } - - private static > void addFlag( long value, EnumSet flags, T flag, int v) { - if ((value & v) != 0) { - flags.add(flag); - } - } - - public Attributes asAttributes() { - Attributes attr = new Attributes(); - // Input flags - long c_iflag = c_iflag(); - EnumSet iflag = attr.getInputFlags(); - addFlag(c_iflag, iflag, Attributes.InputFlag.IGNBRK, IGNBRK); - addFlag(c_iflag, iflag, Attributes.InputFlag.IGNBRK, IGNBRK); - addFlag(c_iflag, iflag, Attributes.InputFlag.BRKINT, BRKINT); - addFlag(c_iflag, iflag, Attributes.InputFlag.IGNPAR, IGNPAR); - addFlag(c_iflag, iflag, Attributes.InputFlag.PARMRK, PARMRK); - addFlag(c_iflag, iflag, Attributes.InputFlag.INPCK, INPCK); - addFlag(c_iflag, iflag, Attributes.InputFlag.ISTRIP, ISTRIP); - addFlag(c_iflag, iflag, Attributes.InputFlag.INLCR, INLCR); - addFlag(c_iflag, iflag, Attributes.InputFlag.IGNCR, IGNCR); - addFlag(c_iflag, iflag, Attributes.InputFlag.ICRNL, ICRNL); - addFlag(c_iflag, iflag, Attributes.InputFlag.IXON, IXON); - addFlag(c_iflag, iflag, Attributes.InputFlag.IXOFF, IXOFF); - addFlag(c_iflag, iflag, Attributes.InputFlag.IXANY, IXANY); - addFlag(c_iflag, iflag, Attributes.InputFlag.IMAXBEL, IMAXBEL); - addFlag(c_iflag, iflag, Attributes.InputFlag.IUTF8, IUTF8); - // Output flags - long c_oflag = c_oflag(); - EnumSet oflag = attr.getOutputFlags(); - addFlag(c_oflag, oflag, Attributes.OutputFlag.OPOST, OPOST); - addFlag(c_oflag, oflag, Attributes.OutputFlag.ONLCR, ONLCR); - addFlag(c_oflag, oflag, Attributes.OutputFlag.OXTABS, OXTABS); - addFlag(c_oflag, oflag, Attributes.OutputFlag.ONOEOT, ONOEOT); - addFlag(c_oflag, oflag, Attributes.OutputFlag.OCRNL, OCRNL); - addFlag(c_oflag, oflag, Attributes.OutputFlag.ONOCR, ONOCR); - addFlag(c_oflag, oflag, Attributes.OutputFlag.ONLRET, ONLRET); - addFlag(c_oflag, oflag, Attributes.OutputFlag.OFILL, OFILL); - addFlag(c_oflag, oflag, Attributes.OutputFlag.NLDLY, NLDLY); - addFlag(c_oflag, oflag, Attributes.OutputFlag.TABDLY, TABDLY); - addFlag(c_oflag, oflag, Attributes.OutputFlag.CRDLY, CRDLY); - addFlag(c_oflag, oflag, Attributes.OutputFlag.FFDLY, FFDLY); - addFlag(c_oflag, oflag, Attributes.OutputFlag.BSDLY, BSDLY); - addFlag(c_oflag, oflag, Attributes.OutputFlag.VTDLY, VTDLY); - addFlag(c_oflag, oflag, Attributes.OutputFlag.OFDEL, OFDEL); - // Control flags - long c_cflag = c_cflag(); - EnumSet cflag = attr.getControlFlags(); - addFlag(c_cflag, cflag, Attributes.ControlFlag.CIGNORE, CIGNORE); - addFlag(c_cflag, cflag, Attributes.ControlFlag.CS5, CS5); - addFlag(c_cflag, cflag, Attributes.ControlFlag.CS6, CS6); - addFlag(c_cflag, cflag, Attributes.ControlFlag.CS7, CS7); - addFlag(c_cflag, cflag, Attributes.ControlFlag.CS8, CS8); - addFlag(c_cflag, cflag, Attributes.ControlFlag.CSTOPB, CSTOPB); - addFlag(c_cflag, cflag, Attributes.ControlFlag.CREAD, CREAD); - addFlag(c_cflag, cflag, Attributes.ControlFlag.PARENB, PARENB); - addFlag(c_cflag, cflag, Attributes.ControlFlag.PARODD, PARODD); - addFlag(c_cflag, cflag, Attributes.ControlFlag.HUPCL, HUPCL); - addFlag(c_cflag, cflag, Attributes.ControlFlag.CLOCAL, CLOCAL); - addFlag(c_cflag, cflag, Attributes.ControlFlag.CCTS_OFLOW, CCTS_OFLOW); - addFlag(c_cflag, cflag, Attributes.ControlFlag.CRTS_IFLOW, CRTS_IFLOW); - addFlag(c_cflag, cflag, Attributes.ControlFlag.CDSR_OFLOW, CDSR_OFLOW); - addFlag(c_cflag, cflag, Attributes.ControlFlag.CCAR_OFLOW, CCAR_OFLOW); - // Local flags - long c_lflag = c_lflag(); - EnumSet lflag = attr.getLocalFlags(); - addFlag(c_lflag, lflag, Attributes.LocalFlag.ECHOKE, ECHOKE); - addFlag(c_lflag, lflag, Attributes.LocalFlag.ECHOE, ECHOE); - addFlag(c_lflag, lflag, Attributes.LocalFlag.ECHOK, ECHOK); - addFlag(c_lflag, lflag, Attributes.LocalFlag.ECHO, ECHO); - addFlag(c_lflag, lflag, Attributes.LocalFlag.ECHONL, ECHONL); - addFlag(c_lflag, lflag, Attributes.LocalFlag.ECHOPRT, ECHOPRT); - addFlag(c_lflag, lflag, Attributes.LocalFlag.ECHOCTL, ECHOCTL); - addFlag(c_lflag, lflag, Attributes.LocalFlag.ISIG, ISIG); - addFlag(c_lflag, lflag, Attributes.LocalFlag.ICANON, ICANON); - addFlag(c_lflag, lflag, Attributes.LocalFlag.ALTWERASE, ALTWERASE); - addFlag(c_lflag, lflag, Attributes.LocalFlag.IEXTEN, IEXTEN); - addFlag(c_lflag, lflag, Attributes.LocalFlag.EXTPROC, EXTPROC); - addFlag(c_lflag, lflag, Attributes.LocalFlag.TOSTOP, TOSTOP); - addFlag(c_lflag, lflag, Attributes.LocalFlag.FLUSHO, FLUSHO); - addFlag(c_lflag, lflag, Attributes.LocalFlag.NOKERNINFO, NOKERNINFO); - addFlag(c_lflag, lflag, Attributes.LocalFlag.PENDIN, PENDIN); - addFlag(c_lflag, lflag, Attributes.LocalFlag.NOFLSH, NOFLSH); - // Control chars - byte[] c_cc = c_cc().toArray(ValueLayout.JAVA_BYTE); - EnumMap cc = attr.getControlChars(); - cc.put(Attributes.ControlChar.VEOF, (int) c_cc[VEOF]); - cc.put(Attributes.ControlChar.VEOL, (int) c_cc[VEOL]); - cc.put(Attributes.ControlChar.VEOL2, (int) c_cc[VEOL2]); - cc.put(Attributes.ControlChar.VERASE, (int) c_cc[VERASE]); - cc.put(Attributes.ControlChar.VWERASE, (int) c_cc[VWERASE]); - cc.put(Attributes.ControlChar.VKILL, (int) c_cc[VKILL]); - cc.put(Attributes.ControlChar.VREPRINT, (int) c_cc[VREPRINT]); - cc.put(Attributes.ControlChar.VINTR, (int) c_cc[VINTR]); - cc.put(Attributes.ControlChar.VQUIT, (int) c_cc[VQUIT]); - cc.put(Attributes.ControlChar.VSUSP, (int) c_cc[VSUSP]); - cc.put(Attributes.ControlChar.VDSUSP, (int) c_cc[VDSUSP]); - cc.put(Attributes.ControlChar.VSTART, (int) c_cc[VSTART]); - cc.put(Attributes.ControlChar.VSTOP, (int) c_cc[VSTOP]); - cc.put(Attributes.ControlChar.VLNEXT, (int) c_cc[VLNEXT]); - cc.put(Attributes.ControlChar.VDISCARD, (int) c_cc[VDISCARD]); - cc.put(Attributes.ControlChar.VMIN, (int) c_cc[VMIN]); - cc.put(Attributes.ControlChar.VTIME, (int) c_cc[VTIME]); - cc.put(Attributes.ControlChar.VSTATUS, (int) c_cc[VSTATUS]); - // Return - return attr; - } - } - - static MethodHandle ioctl; - static MethodHandle isatty; - static MethodHandle openpty; - static MethodHandle tcsetattr; - static MethodHandle tcgetattr; - static MethodHandle ttyname_r; - static { - // methods - Linker linker = Linker.nativeLinker(); - // https://man7.org/linux/man-pages/man2/ioctl.2.html - ioctl = linker.downcallHandle( - linker.defaultLookup().lookup("ioctl").get(), - FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, - ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)); - // https://www.man7.org/linux/man-pages/man3/isatty.3.html - isatty = linker.downcallHandle( - linker.defaultLookup().lookup("isatty").get(), - FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT)); - // https://man7.org/linux/man-pages/man3/openpty.3.html - openpty = linker.downcallHandle( - linker.defaultLookup().lookup("openpty").get(), - FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, - ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.ADDRESS)); - // https://man7.org/linux/man-pages/man3/tcsetattr.3p.html - tcsetattr = linker.downcallHandle( - linker.defaultLookup().lookup("tcsetattr").get(), - FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, - ValueLayout.JAVA_INT, ValueLayout.ADDRESS)); - // https://man7.org/linux/man-pages/man3/tcgetattr.3p.html - tcgetattr = linker.downcallHandle( - linker.defaultLookup().lookup("tcgetattr").get(), - FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, - ValueLayout.ADDRESS)); - // https://man7.org/linux/man-pages/man3/ttyname.3.html - ttyname_r = linker.downcallHandle( - linker.defaultLookup().lookup( "ttyname_r" ).get(), - FunctionDescriptor.of( ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, - ValueLayout.ADDRESS, ValueLayout.JAVA_LONG)); - } - - static Size getTerminalSize(int fd) { - try { - winsize ws = new winsize(); - int res = (int) ioctl.invoke(fd, TIOCGWINSZ, ws.address()); - return new Size( ws.ws_col(), ws.ws_row() ); - } catch (Throwable e) { - throw new RuntimeException("Unable to ioctl(TIOCGWINSZ)", e); - } - } - - static void setTerminalSize(int fd, Size size) { - try { - winsize ws = new winsize(); - ws.ws_row((short) size.getRows()); - ws.ws_col((short) size.getColumns()); - int res = (int) ioctl.invoke(fd, TIOCSWINSZ, ws.address()); - } catch (Throwable e) { - throw new RuntimeException("Unable to ioctl(TIOCGWINSZ)", e); - } - } - - static Attributes getAttributes(int fd) { - try { - termios t = new termios(); - int res = (int) tcgetattr.invoke(fd, t.address()); - return t.asAttributes(); - } catch (Throwable e) { - throw new RuntimeException("Unable to ioctl(TIOCGWINSZ)", e); - } - } - - static void setAttributes(int fd, Attributes attr) { - try { - termios t = new termios(attr); - int res = (int) tcsetattr.invoke(fd, TCSANOW, t.address()); - } catch (Throwable e) { - throw new RuntimeException("Unable to tcsetattr()", e); - } - } - - static boolean isTty(int fd) { - try { - return (int) isatty.invoke(fd) == 1; - } catch (Throwable e) { - throw new RuntimeException("Unable to call isatty", e); - } - } - - static String ttyName(int fd) { - try { - MemorySegment buf = MemorySegment.allocateNative( 64, MemorySession.openImplicit() ); - int res = (int) ttyname_r.invoke(fd, buf, buf.byteSize()); - byte[] data = buf.toArray(ValueLayout.JAVA_BYTE); - int len = 0; - while (data[len] != 0) { - len++; - } - return new String(data, 0, len); - } catch (Throwable e) { - throw new RuntimeException("Unable to call ttyname_r()", e); - } - } - - static Pty openpty(Attributes attr, Size size) { - try { - winsize ws = new winsize(); - termios t = new termios(); - - int[] master = new int[1]; - int[] slave = new int[1]; - byte[] buf = new byte[64]; - int res = (int) openpty.invoke(master, slave, buf, - attr != null ? new termios(attr) : null, - size != null ? new winsize((short) size.getRows(), (short) size.getColumns()) : null); - int len = 0; - while (buf[len] != 0) { - len++; - } - String device = new String(buf, 0, len); - return new NativePty(master[0], NativePty.newDescriptor(master[0]), slave[0], NativePty.newDescriptor(slave[0]), device); - } catch (Throwable e) { - throw new RuntimeException("Unable to call openpty()", e); - } - } - - // CONSTANTS - - private static int TIOCGWINSZ; - private static int TIOCSWINSZ; - - private static int TCSANOW; - private static int TCSADRAIN; - private static int TCSAFLUSH; - - private static int VEOF; - private static int VEOL; - private static int VEOL2; - private static int VERASE; - private static int VWERASE; - private static int VKILL; - private static int VREPRINT; - private static int VERASE2; - private static int VINTR; - private static int VQUIT; - private static int VSUSP; - private static int VDSUSP; - private static int VSTART; - private static int VSTOP; - private static int VLNEXT; - private static int VDISCARD; - private static int VMIN; - private static int VSWTC; - private static int VTIME; - private static int VSTATUS; - - private static int IGNBRK; - private static int BRKINT; - private static int IGNPAR; - private static int PARMRK; - private static int INPCK; - private static int ISTRIP; - private static int INLCR; - private static int IGNCR; - private static int ICRNL; - private static int IUCLC; - private static int IXON; - private static int IXOFF; - private static int IXANY; - private static int IMAXBEL; - private static int IUTF8; - - private static int OPOST; - private static int OLCUC; - private static int ONLCR; - private static int OXTABS; - private static int NLDLY; - private static int NL0; - private static int NL1; - private static int TABDLY; - private static int TAB0; - private static int TAB1; - private static int TAB2; - private static int TAB3; - private static int CRDLY; - private static int CR0; - private static int CR1; - private static int CR2; - private static int CR3; - private static int FFDLY; - private static int FF0; - private static int FF1; - private static int XTABS; - private static int BSDLY; - private static int BS0; - private static int BS1; - private static int VTDLY; - private static int VT0; - private static int VT1; - private static int CBAUD; - private static int B0; - private static int B50; - private static int B75; - private static int B110; - private static int B134; - private static int B150; - private static int B200; - private static int B300; - private static int B600; - private static int B1200; - private static int B1800; - private static int B2400; - private static int B4800; - private static int B9600; - private static int B19200; - private static int B38400; - private static int EXTA; - private static int EXTB; - private static int OFDEL; - private static int ONOEOT; - private static int OCRNL; - private static int ONOCR; - private static int ONLRET; - private static int OFILL; - - private static int CIGNORE; - private static int CSIZE; - private static int CS5; - private static int CS6; - private static int CS7; - private static int CS8; - private static int CSTOPB; - private static int CREAD; - private static int PARENB; - private static int PARODD; - private static int HUPCL; - private static int CLOCAL; - private static int CCTS_OFLOW; - private static int CRTS_IFLOW; - private static int CDTR_IFLOW; - private static int CDSR_OFLOW; - private static int CCAR_OFLOW; - - private static int ECHOKE; - private static int ECHOE; - private static int ECHOK; - private static int ECHO; - private static int ECHONL; - private static int ECHOPRT; - private static int ECHOCTL; - private static int ISIG; - private static int ICANON; - private static int XCASE; - private static int ALTWERASE; - private static int IEXTEN; - private static int EXTPROC; - private static int TOSTOP; - private static int FLUSHO; - private static int NOKERNINFO; - private static int PENDIN; - private static int NOFLSH; - - static { - String osName = System.getProperty("os.name"); - if (osName.startsWith("Linux")) { - String arch = System.getProperty("os.arch"); - boolean isMipsPpcOrSparc = arch.equals("mips") || arch.equals("mips64") - || arch.equals("mipsel") || arch.equals("mips64el") - || arch.startsWith("ppc") || arch.startsWith("sparc"); - TIOCGWINSZ = isMipsPpcOrSparc ? 0x40087468 : 0x00005413; - TIOCSWINSZ = isMipsPpcOrSparc ? 0x80087467 : 0x00005414; - - TCSANOW = 0x0; - TCSADRAIN = 0x1; - TCSAFLUSH = 0x2; - - VINTR = 0; - VQUIT = 1; - VERASE = 2; - VKILL = 3; - VEOF = 4; - VTIME = 5; - VMIN = 6; - VSWTC = 7; - VSTART = 8; - VSTOP = 9; - VSUSP = 10; - VEOL = 11; - VREPRINT = 12; - VDISCARD = 13; - VWERASE = 14; - VLNEXT = 15; - VEOL2 = 16; - - IGNBRK = 0x0000001; - BRKINT = 0x0000002; - IGNPAR = 0x0000004; - PARMRK = 0x0000008; - INPCK = 0x0000010; - ISTRIP = 0x0000020; - INLCR = 0x0000040; - IGNCR = 0x0000080; - ICRNL = 0x0000100; - IUCLC = 0x0000200; - IXON = 0x0000400; - IXANY = 0x0000800; - IXOFF = 0x0001000; - IMAXBEL = 0x0002000; - IUTF8 = 0x0004000; - - OPOST = 0x0000001; - OLCUC = 0x0000002; - ONLCR = 0x0000004; - OCRNL = 0x0000008; - ONOCR = 0x0000010; - ONLRET = 0x0000020; - OFILL = 0x0000040; - OFDEL = 0x0000080; - NLDLY = 0x0000100; - NL0 = 0x0000000; - NL1 = 0x0000100; - CRDLY = 0x0000600; - CR0 = 0x0000000; - CR1 = 0x0000200; - CR2 = 0x0000400; - CR3 = 0x0000600; - TABDLY = 0x0001800; - TAB0 = 0x0000000; - TAB1 = 0x0000800; - TAB2 = 0x0001000; - TAB3 = 0x0001800; - XTABS = 0x0001800; - BSDLY = 0x0002000; - BS0 = 0x0000000; - BS1 = 0x0002000; - VTDLY = 0x0004000; - VT0 = 0x0000000; - VT1 = 0x0004000; - FFDLY = 0x0008000; - FF0 = 0x0000000; - FF1 = 0x0008000; - - CBAUD = 0x000100f; - B0 = 0x0000000; - B50 = 0x0000001; - B75 = 0x0000002; - B110 = 0x0000003; - B134 = 0x0000004; - B150 = 0x0000005; - B200 = 0x0000006; - B300 = 0x0000007; - B600 = 0x0000008; - B1200 = 0x0000009; - B1800 = 0x000000a; - B2400 = 0x000000b; - B4800 = 0x000000c; - B9600 = 0x000000d; - B19200 = 0x000000e; - B38400 = 0x000000f; - EXTA = B19200; - EXTB = B38400; - CSIZE = 0x0000030; - CS5 = 0x0000000; - CS6 = 0x0000010; - CS7 = 0x0000020; - CS8 = 0x0000030; - CSTOPB = 0x0000040; - CREAD = 0x0000080; - PARENB = 0x0000100; - PARODD = 0x0000200; - HUPCL = 0x0000400; - CLOCAL = 0x0000800; - - ISIG = 0x0000001; - ICANON = 0x0000002; - XCASE = 0x0000004; - ECHO = 0x0000008; - ECHOE = 0x0000010; - ECHOK = 0x0000020; - ECHONL = 0x0000040; - NOFLSH = 0x0000080; - TOSTOP = 0x0000100; - ECHOCTL = 0x0000200; - ECHOPRT = 0x0000400; - ECHOKE = 0x0000800; - FLUSHO = 0x0001000; - PENDIN = 0x0002000; - IEXTEN = 0x0008000; - EXTPROC = 0x0010000; - } - else if (osName.startsWith("Solaris") || osName.startsWith("SunOS")) { - int _TIOC = ( 'T' << 8 ); - TIOCGWINSZ = ( _TIOC | 104 ); - TIOCSWINSZ = ( _TIOC | 103 ); - - TCSANOW = 0x0; - TCSADRAIN = 0x1; - TCSAFLUSH = 0x2; - - VINTR = 0; - VQUIT = 1; - VERASE = 2; - VKILL = 3; - VEOF = 4; - VTIME = 5; - VMIN = 6; - VSWTC = 7; - VSTART = 8; - VSTOP = 9; - VSUSP = 10; - VEOL = 11; - VREPRINT = 12; - VDISCARD = 13; - VWERASE = 14; - VLNEXT = 15; - VEOL2 = 16; - - IGNBRK = 0x0000001; - BRKINT = 0x0000002; - IGNPAR = 0x0000004; - PARMRK = 0x0000010; - INPCK = 0x0000020; - ISTRIP = 0x0000040; - INLCR = 0x0000100; - IGNCR = 0x0000200; - ICRNL = 0x0000400; - IUCLC = 0x0001000; - IXON = 0x0002000; - IXANY = 0x0004000; - IXOFF = 0x0010000; - IMAXBEL = 0x0020000; - IUTF8 = 0x0040000; - - OPOST = 0x0000001; - OLCUC = 0x0000002; - ONLCR = 0x0000004; - OCRNL = 0x0000010; - ONOCR = 0x0000020; - ONLRET = 0x0000040; - OFILL = 0x0000100; - OFDEL = 0x0000200; - NLDLY = 0x0000400; - NL0 = 0x0000000; - NL1 = 0x0000400; - CRDLY = 0x0003000; - CR0 = 0x0000000; - CR1 = 0x0001000; - CR2 = 0x0002000; - CR3 = 0x0003000; - TABDLY = 0x0014000; - TAB0 = 0x0000000; - TAB1 = 0x0004000; - TAB2 = 0x0010000; - TAB3 = 0x0014000; - XTABS = 0x0014000; - BSDLY = 0x0020000; - BS0 = 0x0000000; - BS1 = 0x0020000; - VTDLY = 0x0040000; - VT0 = 0x0000000; - VT1 = 0x0040000; - FFDLY = 0x0100000; - FF0 = 0x0000000; - FF1 = 0x0100000; - - CBAUD = 0x0010017; - B0 = 0x0000000; - B50 = 0x0000001; - B75 = 0x0000002; - B110 = 0x0000003; - B134 = 0x0000004; - B150 = 0x0000005; - B200 = 0x0000006; - B300 = 0x0000007; - B600 = 0x0000010; - B1200 = 0x0000011; - B1800 = 0x0000012; - B2400 = 0x0000013; - B4800 = 0x0000014; - B9600 = 0x0000015; - B19200 = 0x0000016; - B38400 = 0x0000017; - EXTA = 0xB19200; - EXTB = 0xB38400; - CSIZE = 0x0000060; - CS5 = 0x0000000; - CS6 = 0x0000020; - CS7 = 0x0000040; - CS8 = 0x0000060; - CSTOPB = 0x0000100; - CREAD = 0x0000200; - PARENB = 0x0000400; - PARODD = 0x0001000; - HUPCL = 0x0002000; - CLOCAL = 0x0004000; - - ISIG = 0x0000001; - ICANON = 0x0000002; - XCASE = 0x0000004; - ECHO = 0x0000010; - ECHOE = 0x0000020; - ECHOK = 0x0000040; - ECHONL = 0x0000100; - NOFLSH = 0x0000200; - TOSTOP = 0x0000400; - ECHOCTL = 0x0001000; - ECHOPRT = 0x0002000; - ECHOKE = 0x0004000; - FLUSHO = 0x0010000; - PENDIN = 0x0040000; - IEXTEN = 0x0100000; - EXTPROC = 0x0200000; - } - else if (osName.startsWith("Mac") || osName.startsWith("Darwin")) { - TIOCGWINSZ = 0x40087468; - TIOCSWINSZ = 0x80087467; - - TCSANOW = 0x00000000; - - VEOF = 0; - VEOL = 1; - VEOL2 = 2; - VERASE = 3; - VWERASE = 4; - VKILL = 5; - VREPRINT = 6; - VINTR = 8; - VQUIT = 9; - VSUSP = 10; - VDSUSP = 11; - VSTART = 12; - VSTOP = 13; - VLNEXT = 14; - VDISCARD = 15; - VMIN = 16; - VTIME = 17; - VSTATUS = 18; - - IGNBRK = 0x00000001; - BRKINT = 0x00000002; - IGNPAR = 0x00000004; - PARMRK = 0x00000008; - INPCK = 0x00000010; - ISTRIP = 0x00000020; - INLCR = 0x00000040; - IGNCR = 0x00000080; - ICRNL = 0x00000100; - IXON = 0x00000200; - IXOFF = 0x00000400; - IXANY = 0x00000800; - IMAXBEL = 0x00002000; - IUTF8 = 0x00004000; - - OPOST = 0x00000001; - ONLCR = 0x00000002; - OXTABS = 0x00000004; - ONOEOT = 0x00000008; - OCRNL = 0x00000010; - ONOCR = 0x00000020; - ONLRET = 0x00000040; - OFILL = 0x00000080; - NLDLY = 0x00000300; - TABDLY = 0x00000c04; - CRDLY = 0x00003000; - FFDLY = 0x00004000; - BSDLY = 0x00008000; - VTDLY = 0x00010000; - OFDEL = 0x00020000; - - CIGNORE = 0x00000001; - CS5 = 0x00000000; - CS6 = 0x00000100; - CS7 = 0x00000200; - CS8 = 0x00000300; - CSTOPB = 0x00000400; - CREAD = 0x00000800; - PARENB = 0x00001000; - PARODD = 0x00002000; - HUPCL = 0x00004000; - CLOCAL = 0x00008000; - CCTS_OFLOW = 0x00010000; - CRTS_IFLOW = 0x00020000; - CDTR_IFLOW = 0x00040000; - CDSR_OFLOW = 0x00080000; - CCAR_OFLOW = 0x00100000; - - ECHOKE = 0x00000001; - ECHOE = 0x00000002; - ECHOK = 0x00000004; - ECHO = 0x00000008; - ECHONL = 0x00000010; - ECHOPRT = 0x00000020; - ECHOCTL = 0x00000040; - ISIG = 0x00000080; - ICANON = 0x00000100; - ALTWERASE = 0x00000200; - IEXTEN = 0x00000400; - EXTPROC = 0x00000800; - TOSTOP = 0x00400000; - FLUSHO = 0x00800000; - NOKERNINFO = 0x02000000; - PENDIN = 0x20000000; - NOFLSH = 0x80000000; - } - else if (osName.startsWith("FreeBSD")) { - TIOCGWINSZ = 0x40087468; - TIOCSWINSZ = 0x80087467; - - TCSANOW = 0x0; - TCSADRAIN = 0x1; - TCSAFLUSH = 0x2; - - VEOF = 0; - VEOL = 1; - VEOL2 = 2; - VERASE = 3; - VWERASE = 4; - VKILL = 5; - VREPRINT = 6; - VERASE2 = 7; - VINTR = 8; - VQUIT = 9; - VSUSP = 10; - VDSUSP = 11; - VSTART = 12; - VSTOP = 13; - VLNEXT = 14; - VDISCARD = 15; - VMIN = 16; - VTIME = 17; - VSTATUS = 18; - - IGNBRK = 0x0000001; - BRKINT = 0x0000002; - IGNPAR = 0x0000004; - PARMRK = 0x0000008; - INPCK = 0x0000010; - ISTRIP = 0x0000020; - INLCR = 0x0000040; - IGNCR = 0x0000080; - ICRNL = 0x0000100; - IXON = 0x0000200; - IXOFF = 0x0000400; - IXANY = 0x0000800; - IMAXBEL = 0x0002000; - - OPOST = 0x0000001; - ONLCR = 0x0000002; - TABDLY = 0x0000004; - TAB0 = 0x0000000; - TAB3 = 0x0000004; - ONOEOT = 0x0000008; - OCRNL = 0x0000010; - ONLRET = 0x0000040; - - CIGNORE = 0x0000001; - CSIZE = 0x0000300; - CS5 = 0x0000000; - CS6 = 0x0000100; - CS7 = 0x0000200; - CS8 = 0x0000300; - CSTOPB = 0x0000400; - CREAD = 0x0000800; - PARENB = 0x0001000; - PARODD = 0x0002000; - HUPCL = 0x0004000; - CLOCAL = 0x0008000; - - ECHOKE = 0x0000001; - ECHOE = 0x0000002; - ECHOK = 0x0000004; - ECHO = 0x0000008; - ECHONL = 0x0000010; - ECHOPRT = 0x0000020; - ECHOCTL = 0x0000040; - ISIG = 0x0000080; - ICANON = 0x0000100; - ALTWERASE = 0x000200; - IEXTEN = 0x0000400; - EXTPROC = 0x0000800; - TOSTOP = 0x0400000; - FLUSHO = 0x0800000; - PENDIN = 0x2000000; - NOFLSH = 0x8000000; - } - else { - throw new UnsupportedOperationException(); - } - } - } - - static class Kernel32 { - - - } } diff --git a/terminal/src/main/java/org/jline/terminal/impl/jep424/Kernel32.java b/terminal/src/main/java/org/jline/terminal/impl/jep424/Kernel32.java new file mode 100644 index 000000000..6f351404b --- /dev/null +++ b/terminal/src/main/java/org/jline/terminal/impl/jep424/Kernel32.java @@ -0,0 +1,1068 @@ +/* + * Copyright (C) 2022 the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jline.terminal.impl.jep424; + +import java.io.IOException; +import java.lang.foreign.Addressable; +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.GroupLayout; +import java.lang.foreign.Linker; +import java.lang.foreign.MemoryAddress; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.MemorySession; +import java.lang.foreign.SymbolLookup; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.VarHandle; +import java.util.Objects; + +import static java.lang.foreign.ValueLayout.JAVA_INT; +import static java.lang.foreign.ValueLayout.OfAddress; +import static java.lang.foreign.ValueLayout.OfBoolean; +import static java.lang.foreign.ValueLayout.OfByte; +import static java.lang.foreign.ValueLayout.OfChar; +import static java.lang.foreign.ValueLayout.OfDouble; +import static java.lang.foreign.ValueLayout.OfFloat; +import static java.lang.foreign.ValueLayout.OfInt; +import static java.lang.foreign.ValueLayout.OfLong; +import static java.lang.foreign.ValueLayout.OfShort; + +@SuppressWarnings( {"unused", "CopyConstructorMissesField"} ) +class Kernel32 +{ + + public static final int FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000; + + public static final int INVALID_HANDLE_VALUE = -1; + public static final int STD_INPUT_HANDLE = -10; + public static final int STD_OUTPUT_HANDLE = -11; + public static final int STD_ERROR_HANDLE = -12; + + public static final int ENABLE_PROCESSED_INPUT = 0x0001; + public static final int ENABLE_LINE_INPUT = 0x0002; + public static final int ENABLE_ECHO_INPUT = 0x0004; + public static final int ENABLE_WINDOW_INPUT = 0x0008; + public static final int ENABLE_MOUSE_INPUT = 0x0010; + public static final int ENABLE_INSERT_MODE = 0x0020; + public static final int ENABLE_QUICK_EDIT_MODE = 0x0040; + public static final int ENABLE_EXTENDED_FLAGS = 0x0080; + + public static final int RIGHT_ALT_PRESSED = 0x0001; + public static final int LEFT_ALT_PRESSED = 0x0002; + public static final int RIGHT_CTRL_PRESSED = 0x0004; + public static final int LEFT_CTRL_PRESSED = 0x0008; + public static final int SHIFT_PRESSED = 0x0010; + + public static final int FOREGROUND_BLUE = 0x0001; + public static final int FOREGROUND_GREEN = 0x0002; + public static final int FOREGROUND_RED = 0x0004; + public static final int FOREGROUND_INTENSITY = 0x0008; + public static final int BACKGROUND_BLUE = 0x0010; + public static final int BACKGROUND_GREEN = 0x0020; + public static final int BACKGROUND_RED = 0x0040; + public static final int BACKGROUND_INTENSITY = 0x0080; + + // Button state + public static final int FROM_LEFT_1ST_BUTTON_PRESSED = 0x0001; + public static final int RIGHTMOST_BUTTON_PRESSED = 0x0002; + public static final int FROM_LEFT_2ND_BUTTON_PRESSED = 0x0004; + public static final int FROM_LEFT_3RD_BUTTON_PRESSED = 0x0008; + public static final int FROM_LEFT_4TH_BUTTON_PRESSED = 0x0010; + + // Event flags + public static final int MOUSE_MOVED = 0x0001; + public static final int DOUBLE_CLICK = 0x0002; + public static final int MOUSE_WHEELED = 0x0004; + public static final int MOUSE_HWHEELED = 0x0008; + + // Event types + public static final short KEY_EVENT = 0x0001; + public static final short MOUSE_EVENT = 0x0002; + public static final short WINDOW_BUFFER_SIZE_EVENT = 0x0004; + public static final short MENU_EVENT = 0x0008; + public static final short FOCUS_EVENT = 0x0010; + + public static int WaitForSingleObject( Addressable hHandle, int dwMilliseconds ) + { + var mh$ = requireNonNull( WaitForSingleObject$MH, "WaitForSingleObject" ); + try + { + return (int) mh$.invokeExact( hHandle, dwMilliseconds ); + } + catch ( Throwable ex$ ) + { + throw new AssertionError( "should not reach here", ex$ ); + } + } + + public static MemoryAddress GetStdHandle( int nStdHandle ) + { + var mh$ = requireNonNull( GetStdHandle$MH, "GetStdHandle" ); + try + { + return (MemoryAddress) mh$.invokeExact( nStdHandle ); + } + catch ( Throwable ex$ ) + { + throw new AssertionError( "should not reach here", ex$ ); + } + } + + public static int FormatMessageW( int dwFlags, Addressable lpSource, int dwMessageId, int dwLanguageId, + Addressable lpBuffer, int nSize, Addressable Arguments ) + { + var mh$ = requireNonNull( FormatMessageW$MH, "FormatMessageW" ); + try + { + return (int) mh$.invokeExact( dwFlags, lpSource, dwMessageId, dwLanguageId, lpBuffer, nSize, Arguments ); + } + catch ( Throwable ex$ ) + { + throw new AssertionError( "should not reach here", ex$ ); + } + } + + public static int SetConsoleTextAttribute( Addressable hConsoleOutput, short wAttributes ) + { + var mh$ = requireNonNull( SetConsoleTextAttribute$MH, "SetConsoleTextAttribute" ); + try + { + return (int) mh$.invokeExact( hConsoleOutput, wAttributes ); + } + catch ( Throwable ex$ ) + { + throw new AssertionError( "should not reach here", ex$ ); + } + } + + public static int SetConsoleMode( Addressable hConsoleHandle, int dwMode ) + { + var mh$ = requireNonNull( SetConsoleMode$MH, "SetConsoleMode" ); + try + { + return (int) mh$.invokeExact( hConsoleHandle, dwMode ); + } + catch ( Throwable ex$ ) + { + throw new AssertionError( "should not reach here", ex$ ); + } + } + + public static int GetConsoleMode( Addressable hConsoleHandle, Addressable lpMode ) + { + var mh$ = requireNonNull( GetConsoleMode$MH, "GetConsoleMode" ); + try + { + return (int) mh$.invokeExact( hConsoleHandle, lpMode ); + } + catch ( Throwable ex$ ) + { + throw new AssertionError( "should not reach here", ex$ ); + } + } + + public static int SetConsoleTitleW( Addressable lpConsoleTitle ) + { + var mh$ = requireNonNull( SetConsoleTitleW$MH, "SetConsoleTitleW" ); + try + { + return (int) mh$.invokeExact( lpConsoleTitle ); + } + catch ( Throwable ex$ ) + { + throw new AssertionError( "should not reach here", ex$ ); + } + } + + public static int SetConsoleCursorPosition( Addressable hConsoleOutput, COORD dwCursorPosition ) + { + var mh$ = requireNonNull( SetConsoleCursorPosition$MH, "SetConsoleCursorPosition" ); + try + { + return (int) mh$.invokeExact( hConsoleOutput, dwCursorPosition.seg ); + } + catch ( Throwable ex$ ) + { + throw new AssertionError( "should not reach here", ex$ ); + } + } + + public static int FillConsoleOutputCharacterW( Addressable hConsoleOutput, char cCharacter, int nLength, + COORD dwWriteCoord, Addressable lpNumberOfCharsWritten ) + { + var mh$ = requireNonNull( FillConsoleOutputCharacterW$MH, "FillConsoleOutputCharacterW" ); + try + { + return (int) mh$.invokeExact( hConsoleOutput, cCharacter, nLength, dwWriteCoord.seg, lpNumberOfCharsWritten ); + } + catch ( Throwable ex$ ) + { + throw new AssertionError( "should not reach here", ex$ ); + } + } + + public static int FillConsoleOutputAttribute( Addressable hConsoleOutput, short wAttribute, int nLength, + COORD dwWriteCoord, Addressable lpNumberOfAttrsWritten ) + { + var mh$ = requireNonNull( FillConsoleOutputAttribute$MH, "FillConsoleOutputAttribute" ); + try + { + return (int) mh$.invokeExact( hConsoleOutput, wAttribute, nLength, dwWriteCoord.seg, lpNumberOfAttrsWritten ); + } + catch ( Throwable ex$ ) + { + throw new AssertionError( "should not reach here", ex$ ); + } + } + + public static int WriteConsoleW( Addressable hConsoleOutput, Addressable lpBuffer, int nNumberOfCharsToWrite, + Addressable lpNumberOfCharsWritten, Addressable lpReserved ) + { + var mh$ = requireNonNull( WriteConsoleW$MH, "WriteConsoleW" ); + try + { + return (int) mh$.invokeExact( hConsoleOutput, lpBuffer, nNumberOfCharsToWrite, lpNumberOfCharsWritten, + lpReserved ); + } + catch ( Throwable ex$ ) + { + throw new AssertionError( "should not reach here", ex$ ); + } + } + + public static int ReadConsoleInputW( Addressable hConsoleInput, Addressable lpBuffer, int nLength, + Addressable lpNumberOfEventsRead ) + { + var mh$ = requireNonNull( ReadConsoleInputW$MH, "ReadConsoleInputW" ); + try + { + return (int) mh$.invokeExact( hConsoleInput, lpBuffer, nLength, lpNumberOfEventsRead ); + } + catch ( Throwable ex$ ) + { + throw new AssertionError( "should not reach here", ex$ ); + } + } + + public static int PeekConsoleInputW( Addressable hConsoleInput, Addressable lpBuffer, int nLength, + Addressable lpNumberOfEventsRead ) + { + var mh$ = requireNonNull( PeekConsoleInputW$MH, "PeekConsoleInputW" ); + try + { + return (int) mh$.invokeExact( hConsoleInput, lpBuffer, nLength, lpNumberOfEventsRead ); + } + catch ( Throwable ex$ ) + { + throw new AssertionError( "should not reach here", ex$ ); + } + } + + public static int GetConsoleScreenBufferInfo ( Addressable hConsoleOutput, CONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo ) { + var mh$ = requireNonNull( GetConsoleScreenBufferInfo$MH, "GetConsoleScreenBufferInfo" ); + try { + return (int)mh$.invokeExact(hConsoleOutput, lpConsoleScreenBufferInfo.seg); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + public static int ScrollConsoleScreenBuffer ( Addressable hConsoleOutput, SMALL_RECT lpScrollRectangle, + SMALL_RECT lpClipRectangle, COORD dwDestinationOrigin, + CHAR_INFO lpFill) { + var mh$ = requireNonNull( ScrollConsoleScreenBuffer$MH, "ScrollConsoleScreenBuffer" ); + try { + return (int)mh$.invokeExact(hConsoleOutput, lpScrollRectangle, lpClipRectangle, dwDestinationOrigin, lpFill); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + public static int GetLastError (Object... x0) { + var mh$ = requireNonNull( GetLastError$MH, "GetLastError" ); + try { + return (int)mh$.invokeExact(x0); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + + public static INPUT_RECORD[] readConsoleInputHelper( MemoryAddress handle, int count, boolean peek ) throws IOException + { + try ( MemorySession session = MemorySession.openImplicit() ) + { + MemorySegment inputRecordPtr = session.allocateArray( INPUT_RECORD.LAYOUT, count ); + MemorySegment length = session.allocate( JAVA_INT, 0 ); + int res = peek ? PeekConsoleInputW( handle, inputRecordPtr, count, length ) + : ReadConsoleInputW( handle, inputRecordPtr, count, length ); + if ( res == 0 ) + { + throw new IOException( "ReadConsoleInputW failed: " + getLastErrorMessage() ); + } + int len = length.get( JAVA_INT, 0 ); + return inputRecordPtr.elements( INPUT_RECORD.LAYOUT ) + .map( INPUT_RECORD::new ) + .limit( len ) + .toArray( INPUT_RECORD[]::new ); + } + } + + public static String getLastErrorMessage() + { + int errorCode = GetLastError(); + return getErrorMessage( errorCode ); + } + + public static String getErrorMessage( int errorCode ) + { + int bufferSize = 160; + MemorySegment data = MemorySegment.allocateNative( bufferSize, MemorySession.openImplicit() ); + FormatMessageW( FORMAT_MESSAGE_FROM_SYSTEM, null, errorCode, 0, data, bufferSize, null ); + return data.getUtf8String( 0 ).trim(); + } + + static final OfBoolean C_BOOL$LAYOUT = ValueLayout.JAVA_BOOLEAN; + static final OfByte C_CHAR$LAYOUT = ValueLayout.JAVA_BYTE; + static final OfChar C_WCHAR$LAYOUT = ValueLayout.JAVA_CHAR.withBitAlignment( 16 ); + static final OfShort C_SHORT$LAYOUT = ValueLayout.JAVA_SHORT.withBitAlignment( 16 ); + static final OfShort C_WORD$LAYOUT = ValueLayout.JAVA_SHORT.withBitAlignment( 16 ); + static final OfInt C_DWORD$LAYOUT = ValueLayout.JAVA_INT.withBitAlignment( 32 ); + static final OfInt C_INT$LAYOUT = JAVA_INT.withBitAlignment( 32 ); + static final OfLong C_LONG$LAYOUT = ValueLayout.JAVA_LONG.withBitAlignment( 64 ); + static final OfLong C_LONG_LONG$LAYOUT = ValueLayout.JAVA_LONG.withBitAlignment( 64 ); + static final OfFloat C_FLOAT$LAYOUT = ValueLayout.JAVA_FLOAT.withBitAlignment( 32 ); + static final OfDouble C_DOUBLE$LAYOUT = ValueLayout.JAVA_DOUBLE.withBitAlignment( 64 ); + static final OfAddress C_POINTER$LAYOUT = ValueLayout.ADDRESS.withBitAlignment( 64 ); + + static final MethodHandle WaitForSingleObject$MH = downcallHandle( + "WaitForSingleObject", + FunctionDescriptor.of( C_INT$LAYOUT, + C_POINTER$LAYOUT, + C_INT$LAYOUT + ) + ); + static final MethodHandle GetStdHandle$MH = downcallHandle( + "GetStdHandle", + FunctionDescriptor.of( C_POINTER$LAYOUT, + C_INT$LAYOUT + ) + ); + static final MethodHandle FormatMessageW$MH = downcallHandle( + "FormatMessageW", + FunctionDescriptor.of( + C_INT$LAYOUT, + C_INT$LAYOUT, + C_POINTER$LAYOUT, + C_INT$LAYOUT, + C_INT$LAYOUT, + C_POINTER$LAYOUT, + C_INT$LAYOUT, + C_POINTER$LAYOUT + ) + ); + static final MethodHandle SetConsoleTextAttribute$MH = downcallHandle( + "SetConsoleTextAttribute", + FunctionDescriptor.of( C_INT$LAYOUT, + C_POINTER$LAYOUT, + C_SHORT$LAYOUT + ) + ); + static final MethodHandle SetConsoleMode$MH = downcallHandle( + "SetConsoleMode", + FunctionDescriptor.of( C_INT$LAYOUT, + C_POINTER$LAYOUT, + C_INT$LAYOUT + ) + ); + static final MethodHandle GetConsoleMode$MH = downcallHandle( + "GetConsoleMode", + FunctionDescriptor.of( C_INT$LAYOUT, + C_POINTER$LAYOUT, + C_POINTER$LAYOUT + ) + ); + + static final MethodHandle SetConsoleTitleW$MH = downcallHandle( + "SetConsoleTitleW", + FunctionDescriptor.of( + C_INT$LAYOUT, + C_POINTER$LAYOUT + ) + ); + static final MethodHandle SetConsoleCursorPosition$MH = downcallHandle( + "SetConsoleCursorPosition", + FunctionDescriptor.of( + C_INT$LAYOUT, + C_POINTER$LAYOUT, + COORD.LAYOUT + ) + ); + static final MethodHandle FillConsoleOutputCharacterW$MH = downcallHandle( + "FillConsoleOutputCharacterW", + FunctionDescriptor.of( + C_INT$LAYOUT, + C_POINTER$LAYOUT, + C_SHORT$LAYOUT, + C_INT$LAYOUT, + COORD.LAYOUT, + C_POINTER$LAYOUT + ) + ); + static final MethodHandle FillConsoleOutputAttribute$MH = downcallHandle( + "FillConsoleOutputAttribute", + FunctionDescriptor.of( + C_INT$LAYOUT, + C_POINTER$LAYOUT, + C_SHORT$LAYOUT, + C_INT$LAYOUT, + COORD.LAYOUT, + C_POINTER$LAYOUT + ) + ); + static final MethodHandle WriteConsoleW$MH = downcallHandle( + "WriteConsoleW", + FunctionDescriptor.of( + C_INT$LAYOUT, + C_POINTER$LAYOUT, + C_POINTER$LAYOUT, + C_INT$LAYOUT, + C_POINTER$LAYOUT, + C_POINTER$LAYOUT + ) + ); + + static final MethodHandle ReadConsoleInputW$MH = downcallHandle( + "ReadConsoleInputW", + FunctionDescriptor.of( + C_INT$LAYOUT, + C_POINTER$LAYOUT, + C_POINTER$LAYOUT, + C_INT$LAYOUT, + C_POINTER$LAYOUT + ) + ); + static final MethodHandle PeekConsoleInputW$MH = downcallHandle( + "PeekConsoleInputW", + FunctionDescriptor.of( + C_INT$LAYOUT, + C_POINTER$LAYOUT, + C_POINTER$LAYOUT, + C_INT$LAYOUT, + C_POINTER$LAYOUT + ) + ); + + static final MethodHandle GetConsoleScreenBufferInfo$MH = downcallHandle( + "GetConsoleScreenBufferInfo", + FunctionDescriptor.of( C_INT$LAYOUT, + C_POINTER$LAYOUT, + C_POINTER$LAYOUT + ) + ); + + static final MethodHandle ScrollConsoleScreenBuffer$MH = downcallHandle( + "ScrollConsoleScreenBuffer", + FunctionDescriptor.of(C_INT$LAYOUT, + C_POINTER$LAYOUT, + C_POINTER$LAYOUT, + C_POINTER$LAYOUT, + COORD.LAYOUT, + C_POINTER$LAYOUT + ) + ); + static final MethodHandle GetLastError$MH = downcallHandle( + "GetLastError", + FunctionDescriptor.of(C_INT$LAYOUT) + ); + + + public static class INPUT_RECORD + { + static final MemoryLayout LAYOUT = MemoryLayout.structLayout( + ValueLayout.JAVA_SHORT.withName( "EventType" ), + MemoryLayout.unionLayout( + KEY_EVENT_RECORD.LAYOUT.withName( "KeyEvent" ), + MOUSE_EVENT_RECORD.LAYOUT.withName( "MouseEvent" ), + WINDOW_BUFFER_SIZE_RECORD.LAYOUT.withName( "WindowBufferSizeEvent" ), + MENU_EVENT_RECORD.LAYOUT.withName( "MenuEvent" ), + FOCUS_EVENT_RECORD.LAYOUT.withName( "FocusEvent" ) + ).withName( "Event" ) + ); + static final VarHandle EventType$VH = varHandle( LAYOUT, "EventType" ); + static final long Event$OFFSET = byteOffset( LAYOUT, "Event" ); + + private final MemorySegment seg; + + public INPUT_RECORD() + { + this( MemorySegment.allocateNative( LAYOUT, MemorySession.openImplicit() ) ); + } + + INPUT_RECORD( MemorySegment seg ) { + this.seg = seg; + } + + public short eventType() + { + return (short) EventType$VH.get( seg ); + } + + public KEY_EVENT_RECORD keyEvent() + { + return new KEY_EVENT_RECORD( seg, Event$OFFSET ); + } + + public MOUSE_EVENT_RECORD mouseEvent() + { + return new MOUSE_EVENT_RECORD( seg, Event$OFFSET ); + } + + public FOCUS_EVENT_RECORD focusEvent() + { + return new FOCUS_EVENT_RECORD( seg, Event$OFFSET ); + } + + } + + public static class MENU_EVENT_RECORD + { + + static final GroupLayout LAYOUT = MemoryLayout.structLayout( + C_DWORD$LAYOUT.withName( "dwCommandId" ) + ); + static final VarHandle COMMAND_ID = varHandle( LAYOUT, "dwCommandId" ); + + private final MemorySegment seg; + + public MENU_EVENT_RECORD() + { + this( MemorySegment.allocateNative( LAYOUT.byteSize(), MemorySession.openImplicit() ) ); + } + + MENU_EVENT_RECORD( MemorySegment seg ) + { + this.seg = seg; + } + + public int commandId() + { + return (int) MENU_EVENT_RECORD.COMMAND_ID.get( seg ); + } + + public void commandId( int commandId ) + { + MENU_EVENT_RECORD.COMMAND_ID.set( seg, commandId ); + } + + } + + public static class FOCUS_EVENT_RECORD + { + + static final GroupLayout LAYOUT = MemoryLayout.structLayout( + C_BOOL$LAYOUT.withName( "bSetFocus" ) + ); + static final VarHandle SET_FOCUS = varHandle( LAYOUT, "bSetFocus" ); + + private final MemorySegment seg; + + public FOCUS_EVENT_RECORD() + { + this( MemorySegment.allocateNative( LAYOUT.byteSize(), MemorySession.openImplicit() ) ); + } + + FOCUS_EVENT_RECORD( MemorySegment seg ) + { + this.seg = Objects.requireNonNull( seg ); + } + + FOCUS_EVENT_RECORD( MemorySegment seg, long offset ) + { + this.seg = Objects.requireNonNull( seg ).asSlice( offset, LAYOUT.byteSize() ); + } + + public boolean setFocus() + { + return (boolean) FOCUS_EVENT_RECORD.SET_FOCUS.get( seg ); + } + + public void setFocus( boolean setFocus ) + { + FOCUS_EVENT_RECORD.SET_FOCUS.set( seg, setFocus ); + } + + } + + public static class WINDOW_BUFFER_SIZE_RECORD + { + + static final GroupLayout LAYOUT = MemoryLayout.structLayout( + COORD.LAYOUT.withName( "size" ) + ); + static final long SIZE_OFFSET = byteOffset( LAYOUT, "size" ); + + private final MemorySegment seg; + + public WINDOW_BUFFER_SIZE_RECORD() + { + this( MemorySegment.allocateNative( LAYOUT.byteSize(), MemorySession.openImplicit() ) ); + } + + WINDOW_BUFFER_SIZE_RECORD( MemorySegment seg ) + { + this.seg = seg; + } + + public COORD size() + { + return new COORD( seg, SIZE_OFFSET ); + } + + public String toString() + { + return "WINDOW_BUFFER_SIZE_RECORD{size=" + this.size() + '}'; + } + + } + + public static class MOUSE_EVENT_RECORD + { + + private static final MemoryLayout LAYOUT = MemoryLayout.structLayout( + COORD.LAYOUT.withName( "dwMousePosition" ), + C_DWORD$LAYOUT.withName( "dwButtonState" ), + C_DWORD$LAYOUT.withName( "dwControlKeyState" ), + C_DWORD$LAYOUT.withName( "dwEventFlags" ) + ); + private static final long MOUSE_POSITION_OFFSET = byteOffset( LAYOUT, "dwMousePosition" ); + private static final VarHandle BUTTON_STATE = varHandle( LAYOUT, "dwButtonState" ); + private static final VarHandle CONTROL_KEY_STATE = varHandle( LAYOUT, "dwControlKeyState" ); + private static final VarHandle EVENT_FLAGS = varHandle( LAYOUT, "dwEventFlags" ); + + private final MemorySegment seg; + + public MOUSE_EVENT_RECORD() + { + this( MemorySegment.allocateNative( LAYOUT, MemorySession.openImplicit() ) ); + } + + MOUSE_EVENT_RECORD( MemorySegment seg ) + { + this.seg = Objects.requireNonNull( seg ); + } + + MOUSE_EVENT_RECORD( MemorySegment seg, long offset ) + { + this.seg = Objects.requireNonNull( seg ).asSlice( offset, LAYOUT.byteSize() ); + } + + public COORD mousePosition() + { + return new COORD( seg, MOUSE_POSITION_OFFSET ); + } + + public int buttonState() + { + return (int) BUTTON_STATE.get( seg ); + } + + public int controlKeyState() + { + return (int) CONTROL_KEY_STATE.get( seg ); + } + + public int eventFlags() + { + return (int) EVENT_FLAGS.get( seg ); + } + + public String toString() + { + return "MOUSE_EVENT_RECORD{mousePosition=" + mousePosition() + ", buttonState=" + buttonState() + + ", controlKeyState=" + controlKeyState() + ", eventFlags=" + eventFlags() + '}'; + } + } + + public static class KEY_EVENT_RECORD + { + + static final MemoryLayout LAYOUT = MemoryLayout.structLayout( + JAVA_INT.withName( "bKeyDown" ), + ValueLayout.JAVA_SHORT.withName( "wRepeatCount" ), + ValueLayout.JAVA_SHORT.withName( "wVirtualKeyCode" ), + ValueLayout.JAVA_SHORT.withName( "wVirtualScanCode" ), + MemoryLayout.unionLayout( + ValueLayout.JAVA_CHAR.withName( "UnicodeChar" ), + ValueLayout.JAVA_BYTE.withName( "AsciiChar" ) + ).withName( "uChar" ), + JAVA_INT.withName( "dwControlKeyState" ) + ); + static final VarHandle bKeyDown$VH = varHandle( LAYOUT, "bKeyDown" ); + static final VarHandle wRepeatCount$VH = varHandle( LAYOUT, "wRepeatCount" ); + static final VarHandle wVirtualKeyCode$VH = varHandle( LAYOUT, "wVirtualKeyCode" ); + static final VarHandle wVirtualScanCode$VH = varHandle( LAYOUT, "wVirtualScanCode" ); + static final VarHandle UnicodeChar$VH = varHandle( LAYOUT, "uChar", "UnicodeChar" ); + static final VarHandle AsciiChar$VH = varHandle( LAYOUT, "uChar", "AsciiChar" ); + static final VarHandle dwControlKeyState$VH = varHandle( LAYOUT, "dwControlKeyState" ); + + final MemorySegment seg; + + public KEY_EVENT_RECORD() + { + this( MemorySegment.allocateNative( LAYOUT, MemorySession.openImplicit() ) ); + } + + KEY_EVENT_RECORD( MemorySegment seg ) + { + this.seg = seg; + } + + KEY_EVENT_RECORD( MemorySegment seg, long offset ) + { + this.seg = Objects.requireNonNull( seg ).asSlice( offset, LAYOUT.byteSize() ); + } + + public boolean keyDown() + { + return (boolean) bKeyDown$VH.get( seg ); + } + + public int repeatCount() + { + return (int) wRepeatCount$VH.get( seg ); + } + + public short keyCode() + { + return (short) wVirtualKeyCode$VH.get( seg ); + } + + public short scanCode() + { + return (short) wVirtualScanCode$VH.get( seg ); + } + + public char uchar() + { + return (char) UnicodeChar$VH.get( seg ); + } + + public int controlKeyState() + { + return (int) dwControlKeyState$VH.get( seg ); + } + + public String toString() + { + return "KEY_EVENT_RECORD{keyDown=" + this.keyDown() + ", repeatCount=" + this.repeatCount() + ", keyCode=" + + this.keyCode() + ", scanCode=" + this.scanCode() + ", uchar=" + this.uchar() + + ", controlKeyState=" + + this.controlKeyState() + '}'; + } + } + + public static class CHAR_INFO + { + + static final GroupLayout LAYOUT = MemoryLayout.structLayout( + MemoryLayout.unionLayout( + C_WCHAR$LAYOUT.withName( "UnicodeChar" ), + C_CHAR$LAYOUT.withName( "AsciiChar" ) + ).withName( "Char" ), + C_WORD$LAYOUT.withName( "Attributes" ) + ); + static final VarHandle UnicodeChar$VH = varHandle( LAYOUT, "Char", "UnicodeChar" ); + static final VarHandle Attributes$VH = varHandle( LAYOUT, "Attributes" ); + + final MemorySegment seg; + + public CHAR_INFO() + { + this( MemorySegment.allocateNative( LAYOUT.byteSize(), MemorySession.openImplicit() ) ); + } + + public CHAR_INFO( char c, short a ) + { + this(); + UnicodeChar$VH.set( seg, c ); + Attributes$VH.set( seg, a ); + } + + CHAR_INFO( MemorySegment seg ) + { + this.seg = seg; + } + + public char unicodeChar() + { + return (char) UnicodeChar$VH.get( seg ); + } + + } + + public static class CONSOLE_SCREEN_BUFFER_INFO + { + static final GroupLayout LAYOUT = MemoryLayout.structLayout( + COORD.LAYOUT.withName( "dwSize" ), + COORD.LAYOUT.withName( "dwCursorPosition" ), + C_WORD$LAYOUT.withName( "wAttributes" ), + SMALL_RECT.LAYOUT.withName( "srWindow" ), + COORD.LAYOUT.withName( "dwMaximumWindowSize" ) + ); + static final long dwSize$OFFSET = byteOffset( LAYOUT, "dwSize" ); + static final long dwCursorPosition$OFFSET = byteOffset( LAYOUT, "dwCursorPosition" ); + static final VarHandle wAttributes$VH = varHandle( LAYOUT, "wAttributes" ); + static final long srWindow$OFFSET = byteOffset( LAYOUT, "srWindow" ); + + private final MemorySegment seg; + + public CONSOLE_SCREEN_BUFFER_INFO() + { + this( MemorySegment.allocateNative( LAYOUT.byteSize(), MemorySession.openImplicit() ) ); + } + + CONSOLE_SCREEN_BUFFER_INFO( MemorySegment seg ) + { + this.seg = seg; + } + + public COORD size() + { + return new COORD( seg, dwSize$OFFSET ); + } + + public COORD cursorPosition() + { + return new COORD( seg, dwCursorPosition$OFFSET ); + } + + public short attributes() + { + return (short) wAttributes$VH.get( seg ); + } + + public SMALL_RECT window() + { + return new SMALL_RECT( seg, srWindow$OFFSET ); + } + + public int windowWidth() + { + return this.window().width() + 1; + } + + public int windowHeight() + { + return this.window().height() + 1; + } + + public void attributes( short attr ) + { + wAttributes$VH.set( seg, attr ); + } + } + + public static class COORD + { + + static final GroupLayout LAYOUT = MemoryLayout.structLayout( + C_SHORT$LAYOUT.withName( "x" ), + C_SHORT$LAYOUT.withName( "y" ) + ); + static final VarHandle x$VH = varHandle( LAYOUT, "x" ); + static final VarHandle y$VH = varHandle( LAYOUT, "y" ); + + private final MemorySegment seg; + + public COORD() + { + this( MemorySegment.allocateNative( LAYOUT, MemorySession.openImplicit() ) ); + } + + public COORD( short x, short y ) + { + this( MemorySegment.allocateNative( LAYOUT, MemorySession.openImplicit() ) ); + x( x ); + y( y ); + } + + public COORD( COORD from ) + { + this( MemorySegment.allocateNative( LAYOUT, MemorySession.openImplicit() ) + .copyFrom( Objects.requireNonNull( from ).seg ) ); + } + + COORD( MemorySegment seg ) + { + this.seg = seg; + } + + COORD( MemorySegment seg, long offset ) + { + this.seg = Objects.requireNonNull( seg ).asSlice( offset, LAYOUT.byteSize() ); + } + + public short x() + { + return (short) COORD.x$VH.get( seg ); + } + + public void x( short x ) + { + COORD.x$VH.set( seg, x ); + } + + public short y() + { + return (short) COORD.y$VH.get( seg ); + } + + public void y( short y ) + { + COORD.y$VH.set( seg, y ); + } + + public COORD copy() + { + return new COORD( this ); + } + } + + public static class SMALL_RECT + { + + static final GroupLayout LAYOUT = MemoryLayout.structLayout( + C_SHORT$LAYOUT.withName( "Left" ), + C_SHORT$LAYOUT.withName( "Top" ), + C_SHORT$LAYOUT.withName( "Right" ), + C_SHORT$LAYOUT.withName( "Bottom" ) + ); + static final VarHandle Left$VH = varHandle( LAYOUT, "Left" ); + static final VarHandle Top$VH = varHandle( LAYOUT, "Top" ); + static final VarHandle Right$VH = varHandle( LAYOUT, "Right" ); + static final VarHandle Bottom$VH = varHandle( LAYOUT, "Bottom" ); + + private final MemorySegment seg; + + public SMALL_RECT() + { + this( MemorySegment.allocateNative( LAYOUT, MemorySession.openImplicit() ) ); + } + + public SMALL_RECT( SMALL_RECT from ) + { + this( MemorySegment.allocateNative( LAYOUT, MemorySession.openImplicit() ) + .copyFrom( from.seg ) ); + } + + SMALL_RECT( MemorySegment seg, long offset ) + { + this( seg.asSlice( offset, LAYOUT.byteSize() ) ); + } + + SMALL_RECT( MemorySegment seg ) + { + this.seg = seg; + } + + public short left() + { + return (short) Left$VH.get( seg ); + } + + public short top() + { + return (short) Top$VH.get( seg ); + } + + public short right() + { + return (short) Right$VH.get( seg ); + } + + public short bottom() + { + return (short) Bottom$VH.get( seg ); + } + + public short width() + { + return (short) ( this.right() - this.left() ); + } + + public short height() + { + return (short) ( this.bottom() - this.top() ); + } + + public void left( short l ) + { + Left$VH.set( seg, l ); + } + + public void top( short t ) + { + Top$VH.set( seg, t ); + } + + public SMALL_RECT copy() + { + return new SMALL_RECT( this ); + } + } + + private final static Linker LINKER = Linker.nativeLinker(); + + private final static SymbolLookup SYMBOL_LOOKUP; + + static + { + SymbolLookup loaderLookup = SymbolLookup.loaderLookup(); + SYMBOL_LOOKUP = name -> loaderLookup.lookup( name ).or( () -> LINKER.defaultLookup().lookup( name ) ); + } + + static MethodHandle downcallHandle( String name, FunctionDescriptor fdesc ) + { + return SYMBOL_LOOKUP.lookup( name ) + .map( addr -> LINKER.downcallHandle( addr, fdesc ) ) + .orElse( null ); + } + + static T requireNonNull( T obj, String symbolName ) + { + if ( obj == null ) + { + throw new UnsatisfiedLinkError( "unresolved symbol: " + symbolName ); + } + return obj; + } + + static VarHandle varHandle( MemoryLayout layout, String e1 ) { + return layout.varHandle( MemoryLayout.PathElement.groupElement( e1 ) ); + } + + static VarHandle varHandle( MemoryLayout layout, String e1, String e2 ) { + return layout.varHandle( MemoryLayout.PathElement.groupElement( e1 ), + MemoryLayout.PathElement.groupElement( e2 ) ); + } + + static long byteOffset( MemoryLayout layout, String e1 ) { + return layout.byteOffset( MemoryLayout.PathElement.groupElement( e1 ) ); + } + +} diff --git a/terminal/src/main/java/org/jline/terminal/impl/jep424/NativePty.java b/terminal/src/main/java/org/jline/terminal/impl/jep424/NativePty.java new file mode 100644 index 000000000..0958bcdae --- /dev/null +++ b/terminal/src/main/java/org/jline/terminal/impl/jep424/NativePty.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2022 the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jline.terminal.impl.jep424; + +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Constructor; + +import org.jline.terminal.Attributes; +import org.jline.terminal.Size; +import org.jline.terminal.impl.AbstractPty; +import org.jline.terminal.spi.TerminalProvider; + +class NativePty extends AbstractPty +{ + private final int master; + private final int slave; + private final int slaveOut; + private final String name; + private final FileDescriptor masterFD; + private final FileDescriptor slaveFD; + private final FileDescriptor slaveOutFD; + + public NativePty( int master, FileDescriptor masterFD, int slave, FileDescriptor slaveFD, String name ) + { + this( master, masterFD, slave, slaveFD, slave, slaveFD, name ); + } + + public NativePty( int master, FileDescriptor masterFD, int slave, FileDescriptor slaveFD, int slaveOut, + FileDescriptor slaveOutFD, String name ) + { + this.master = master; + this.slave = slave; + this.slaveOut = slaveOut; + this.name = name; + this.masterFD = masterFD; + this.slaveFD = slaveFD; + this.slaveOutFD = slaveOutFD; + } + + @Override + public void close() throws IOException + { + if ( master > 0 ) + { + getMasterInput().close(); + } + if ( slave > 0 ) + { + getSlaveInput().close(); + } + } + + public int getMaster() + { + return master; + } + + public int getSlave() + { + return slave; + } + + public int getSlaveOut() + { + return slaveOut; + } + + public String getName() + { + return name; + } + + public FileDescriptor getMasterFD() + { + return masterFD; + } + + public FileDescriptor getSlaveFD() + { + return slaveFD; + } + + public FileDescriptor getSlaveOutFD() + { + return slaveOutFD; + } + + public InputStream getMasterInput() + { + return new FileInputStream( getMasterFD() ); + } + + public OutputStream getMasterOutput() + { + return new FileOutputStream( getMasterFD() ); + } + + protected InputStream doGetSlaveInput() + { + return new FileInputStream( getSlaveFD() ); + } + + public OutputStream getSlaveOutput() + { + return new FileOutputStream( getSlaveOutFD() ); + } + + @Override + public Attributes getAttr() throws IOException + { + return CLibrary.getAttributes( slave ); + } + + @Override + protected void doSetAttr( Attributes attr ) throws IOException + { + CLibrary.setAttributes( slave, attr ); + } + + @Override + public Size getSize() throws IOException + { + return CLibrary.getTerminalSize( slave ); + } + + @Override + public void setSize( Size size ) throws IOException + { + CLibrary.setTerminalSize( slave, size ); + } + + @Override + public String toString() + { + return "NativePty[" + getName() + "]"; + } + + protected static FileDescriptor newDescriptor( int fd ) + { + try + { + Constructor cns = FileDescriptor.class.getDeclaredConstructor( int.class ); + cns.setAccessible( true ); + return cns.newInstance( fd ); + } + catch ( Throwable e ) + { + throw new RuntimeException( "Unable to create FileDescriptor", e ); + } + } + + public static boolean isPosixSystemStream( TerminalProvider.Stream stream ) + { + return switch ( stream ) + { + case Input -> CLibrary.isTty( 0 ); + case Output -> CLibrary.isTty( 1 ); + case Error -> CLibrary.isTty( 2 ); + }; + } + + public static String posixSystemStreamName( TerminalProvider.Stream stream ) + { + return switch ( stream ) + { + case Input -> CLibrary.ttyName( 0 ); + case Output -> CLibrary.ttyName( 1 ); + case Error -> CLibrary.ttyName( 2 ); + }; + } +} diff --git a/terminal/src/main/java/org/jline/terminal/impl/jep424/NativeWinConsoleWriter.java b/terminal/src/main/java/org/jline/terminal/impl/jep424/NativeWinConsoleWriter.java new file mode 100644 index 000000000..f9ddb3395 --- /dev/null +++ b/terminal/src/main/java/org/jline/terminal/impl/jep424/NativeWinConsoleWriter.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2022 the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jline.terminal.impl.jep424; + +import java.io.IOException; +import java.lang.foreign.MemoryAddress; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.MemorySession; +import java.lang.foreign.ValueLayout; + +import org.jline.terminal.impl.AbstractWindowsConsoleWriter; + +import static org.jline.terminal.impl.jep424.Kernel32.GetStdHandle; +import static org.jline.terminal.impl.jep424.Kernel32.STD_OUTPUT_HANDLE; +import static org.jline.terminal.impl.jep424.Kernel32.WriteConsoleW; +import static org.jline.terminal.impl.jep424.Kernel32.getLastErrorMessage; + +class NativeWinConsoleWriter extends AbstractWindowsConsoleWriter +{ + + private final MemoryAddress console = GetStdHandle( STD_OUTPUT_HANDLE ); + + @Override + protected void writeConsole( char[] text, int len ) throws IOException + { + MemorySegment txt = MemorySegment.ofArray( text ); + if ( WriteConsoleW( console, txt, len, null, null ) == 0 ) + { + throw new IOException( "Failed to write to console: " + getLastErrorMessage() ); + } + } +} diff --git a/terminal/src/main/java/org/jline/terminal/impl/jep424/NativeWinSysTerminal.java b/terminal/src/main/java/org/jline/terminal/impl/jep424/NativeWinSysTerminal.java new file mode 100644 index 000000000..8a94110e7 --- /dev/null +++ b/terminal/src/main/java/org/jline/terminal/impl/jep424/NativeWinSysTerminal.java @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2022 the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jline.terminal.impl.jep424; + +import java.io.BufferedWriter; +import java.io.IOError; +import java.io.IOException; +import java.io.Writer; +import java.lang.foreign.MemoryAddress; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.MemorySession; +import java.nio.charset.Charset; +import java.util.function.IntConsumer; + +import org.jline.terminal.Cursor; +import org.jline.terminal.Size; +import org.jline.terminal.impl.AbstractWindowsTerminal; +import org.jline.terminal.spi.TerminalProvider; +import org.jline.utils.OSUtils; + +import static java.lang.foreign.ValueLayout.JAVA_INT; +import static org.jline.terminal.impl.jep424.Kernel32.*; +import static org.jline.terminal.impl.jep424.Kernel32.GetConsoleMode; +import static org.jline.terminal.impl.jep424.Kernel32.GetConsoleScreenBufferInfo; +import static org.jline.terminal.impl.jep424.Kernel32.GetStdHandle; +import static org.jline.terminal.impl.jep424.Kernel32.INPUT_RECORD; +import static org.jline.terminal.impl.jep424.Kernel32.INVALID_HANDLE_VALUE; +import static org.jline.terminal.impl.jep424.Kernel32.KEY_EVENT_RECORD; +import static org.jline.terminal.impl.jep424.Kernel32.MOUSE_EVENT_RECORD; +import static org.jline.terminal.impl.jep424.Kernel32.STD_ERROR_HANDLE; +import static org.jline.terminal.impl.jep424.Kernel32.STD_INPUT_HANDLE; +import static org.jline.terminal.impl.jep424.Kernel32.STD_OUTPUT_HANDLE; +import static org.jline.terminal.impl.jep424.Kernel32.SetConsoleMode; +import static org.jline.terminal.impl.jep424.Kernel32.WaitForSingleObject; +import static org.jline.terminal.impl.jep424.Kernel32.getLastErrorMessage; +import static org.jline.terminal.impl.jep424.Kernel32.readConsoleInputHelper; + +public class NativeWinSysTerminal extends AbstractWindowsTerminal +{ + + public static NativeWinSysTerminal createTerminal( String name, String type, boolean ansiPassThrough, + Charset encoding, + boolean nativeSignals, SignalHandler signalHandler, + boolean paused, + TerminalProvider.Stream consoleStream ) throws IOException + { + Writer writer; + MemorySegment mode = MemorySegment.allocateNative( JAVA_INT, MemorySession.openImplicit() ); + MemoryAddress consoleIn = GetStdHandle( STD_INPUT_HANDLE ); + MemoryAddress console; + switch ( consoleStream ) + { + case Output: + console = GetStdHandle( STD_OUTPUT_HANDLE ); + break; + case Error: + console = GetStdHandle( STD_ERROR_HANDLE ); + break; + default: + throw new IllegalArgumentException( "Unsupport stream for console: " + consoleStream ); + } + if ( ansiPassThrough ) + { + if ( type == null ) + { + type = OSUtils.IS_CONEMU ? TYPE_WINDOWS_CONEMU : TYPE_WINDOWS; + } + writer = new NativeWinConsoleWriter(); + } + else + { + if ( GetConsoleMode( console, mode ) == 0 ) + { + throw new IOException( "Failed to get console mode: " + getLastErrorMessage() ); + } + int m = mode.get( JAVA_INT, 0 ); + if ( SetConsoleMode( console, + m | AbstractWindowsTerminal.ENABLE_VIRTUAL_TERMINAL_PROCESSING ) != 0 ) + { + if ( type == null ) + { + type = TYPE_WINDOWS_VTP; + } + writer = new NativeWinConsoleWriter(); + } + else if ( OSUtils.IS_CONEMU ) + { + if ( type == null ) + { + type = TYPE_WINDOWS_CONEMU; + } + writer = new NativeWinConsoleWriter(); + } + else + { + if ( type == null ) + { + type = TYPE_WINDOWS; + } + writer = new WindowsAnsiWriter( new BufferedWriter( new NativeWinConsoleWriter() ) ); + } + } + if ( GetConsoleMode( consoleIn, mode ) == 0 ) + { + throw new IOException( "Failed to get console mode: " + getLastErrorMessage() ); + } + NativeWinSysTerminal terminal = new NativeWinSysTerminal( writer, name, type, encoding, nativeSignals, + signalHandler, consoleIn, console ); + // Start input pump thread + if ( !paused ) + { + terminal.resume(); + } + return terminal; + } + + public static boolean isWindowsSystemStream( TerminalProvider.Stream stream ) + { + MemoryAddress console; + MemorySegment mode = MemorySegment.allocateNative( JAVA_INT, MemorySession.openImplicit() ); + switch ( stream ) + { + case Input: + console = GetStdHandle( STD_INPUT_HANDLE ); + break; + case Output: + console = GetStdHandle( STD_OUTPUT_HANDLE ); + break; + case Error: + console = GetStdHandle( STD_ERROR_HANDLE ); + break; + default: + return false; + } + return GetConsoleMode( console, mode ) != 0; + } + + private final MemoryAddress console; + private final MemoryAddress outputHandle; + + NativeWinSysTerminal( Writer writer, String name, String type, Charset encoding, boolean nativeSignals, + SignalHandler signalHandler, + MemoryAddress console, MemoryAddress outputHandle ) throws IOException + { + super( writer, name, type, encoding, nativeSignals, signalHandler ); + this.console = console; + this.outputHandle = outputHandle; + } + + @Override + protected int getConsoleMode() + { + try (MemorySession session = MemorySession.openImplicit() ) + { + MemorySegment mode = session.allocate( JAVA_INT ); + if ( GetConsoleMode( console, mode ) == 0 ) + { + return -1; + } + return mode.get( JAVA_INT, 0 ); + } + } + + @Override + protected void setConsoleMode( int mode ) + { + SetConsoleMode( console, mode ); + } + + public Size getSize() + { + CONSOLE_SCREEN_BUFFER_INFO info = new CONSOLE_SCREEN_BUFFER_INFO(); + GetConsoleScreenBufferInfo( outputHandle, info ); + return new Size( info.windowWidth(), info.windowHeight() ); + } + + @Override + public Size getBufferSize() + { + CONSOLE_SCREEN_BUFFER_INFO info = new CONSOLE_SCREEN_BUFFER_INFO(); + GetConsoleScreenBufferInfo( outputHandle, info ); + return new Size( info.size().x(), info.size().y() ); + } + + protected boolean processConsoleInput() throws IOException + { + INPUT_RECORD[] events; + if ( console != null && console.toRawLongValue() != INVALID_HANDLE_VALUE + && WaitForSingleObject( console, 100 ) == 0 ) + { + events = readConsoleInputHelper( console, 1, false ); + } + else + { + return false; + } + + boolean flush = false; + for ( INPUT_RECORD event : events ) + { + int eventType = event.eventType(); + if ( eventType == KEY_EVENT ) + { + KEY_EVENT_RECORD keyEvent = event.keyEvent(); + processKeyEvent( keyEvent.keyDown(), keyEvent.keyCode(), keyEvent.uchar(), keyEvent.controlKeyState() ); + flush = true; + } + else if ( eventType == WINDOW_BUFFER_SIZE_EVENT ) + { + raise( Signal.WINCH ); + } + else if ( eventType == MOUSE_EVENT ) + { + processMouseEvent( event.mouseEvent() ); + flush = true; + } + else if ( eventType == FOCUS_EVENT ) + { + processFocusEvent( event.focusEvent().setFocus() ); + } + } + + return flush; + } + + private final char[] focus = new char[] {'\033', '[', ' '}; + + private void processFocusEvent( boolean hasFocus ) throws IOException + { + if ( focusTracking ) + { + focus[2] = hasFocus ? 'I' : 'O'; + slaveInputPipe.write( focus ); + } + } + + private final char[] mouse = new char[] {'\033', '[', 'M', ' ', ' ', ' '}; + + private void processMouseEvent( MOUSE_EVENT_RECORD mouseEvent ) throws IOException + { + int dwEventFlags = mouseEvent.eventFlags(); + int dwButtonState = mouseEvent.buttonState(); + if ( tracking == MouseTracking.Off + || tracking == MouseTracking.Normal && dwEventFlags == MOUSE_MOVED + || tracking == MouseTracking.Button && dwEventFlags == MOUSE_MOVED + && dwButtonState == 0 ) + { + return; + } + int cb = 0; + dwEventFlags &= ~DOUBLE_CLICK; // Treat double-clicks as normal + if ( dwEventFlags == MOUSE_WHEELED ) + { + cb |= 64; + if ( ( dwButtonState >> 16 ) < 0 ) + { + cb |= 1; + } + } + else if ( dwEventFlags == MOUSE_HWHEELED ) + { + return; + } + else if ( ( dwButtonState & FROM_LEFT_1ST_BUTTON_PRESSED ) != 0 ) + { + cb |= 0x00; + } + else if ( ( dwButtonState & RIGHTMOST_BUTTON_PRESSED ) != 0 ) + { + cb |= 0x01; + } + else if ( ( dwButtonState & 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( outputHandle, info ) == 0 ) + { + throw new IOError( new IOException( "Could not get the cursor position: " + getLastErrorMessage() ) ); + } + return new Cursor( info.cursorPosition().x(), info.cursorPosition().y() ); + } + +} diff --git a/terminal/src/main/java/org/jline/terminal/impl/jep424/WindowsAnsiWriter.java b/terminal/src/main/java/org/jline/terminal/impl/jep424/WindowsAnsiWriter.java new file mode 100644 index 000000000..80f0af9ee --- /dev/null +++ b/terminal/src/main/java/org/jline/terminal/impl/jep424/WindowsAnsiWriter.java @@ -0,0 +1,409 @@ +/* + * Copyright (C) 2009-2018 the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jline.terminal.impl.jep424; + +import java.io.IOException; +import java.io.Writer; +import java.lang.foreign.MemoryAddress; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.MemorySession; +import java.lang.foreign.ValueLayout; + +import org.jline.utils.AnsiWriter; +import org.jline.utils.Colors; + +import static org.jline.terminal.impl.jep424.Kernel32.BACKGROUND_BLUE; +import static org.jline.terminal.impl.jep424.Kernel32.BACKGROUND_GREEN; +import static org.jline.terminal.impl.jep424.Kernel32.BACKGROUND_INTENSITY; +import static org.jline.terminal.impl.jep424.Kernel32.BACKGROUND_RED; +import static org.jline.terminal.impl.jep424.Kernel32.CHAR_INFO; +import static org.jline.terminal.impl.jep424.Kernel32.CONSOLE_SCREEN_BUFFER_INFO; +import static org.jline.terminal.impl.jep424.Kernel32.COORD; +import static org.jline.terminal.impl.jep424.Kernel32.FOREGROUND_BLUE; +import static org.jline.terminal.impl.jep424.Kernel32.FOREGROUND_GREEN; +import static org.jline.terminal.impl.jep424.Kernel32.FOREGROUND_INTENSITY; +import static org.jline.terminal.impl.jep424.Kernel32.FOREGROUND_RED; +import static org.jline.terminal.impl.jep424.Kernel32.FillConsoleOutputAttribute; +import static org.jline.terminal.impl.jep424.Kernel32.FillConsoleOutputCharacterW; +import static org.jline.terminal.impl.jep424.Kernel32.GetConsoleScreenBufferInfo; +import static org.jline.terminal.impl.jep424.Kernel32.GetStdHandle; +import static org.jline.terminal.impl.jep424.Kernel32.SMALL_RECT; +import static org.jline.terminal.impl.jep424.Kernel32.STD_OUTPUT_HANDLE; +import static org.jline.terminal.impl.jep424.Kernel32.ScrollConsoleScreenBuffer; +import static org.jline.terminal.impl.jep424.Kernel32.SetConsoleCursorPosition; +import static org.jline.terminal.impl.jep424.Kernel32.SetConsoleTextAttribute; +import static org.jline.terminal.impl.jep424.Kernel32.SetConsoleTitleW; +import static org.jline.terminal.impl.jep424.Kernel32.getLastErrorMessage; + +/** + * A Windows ANSI escape processor, that uses JNA to access native platform + * API's to change the console attributes. + * + * @since 1.0 + * @author Hiram Chirino + * @author Joris Kuipers + */ +public final class WindowsAnsiWriter extends AnsiWriter { + + private static final MemoryAddress console = GetStdHandle(STD_OUTPUT_HANDLE); + + private static final short FOREGROUND_BLACK = 0; + private static final short FOREGROUND_YELLOW = (short) (FOREGROUND_RED | FOREGROUND_GREEN); + private static final short FOREGROUND_MAGENTA = (short) (FOREGROUND_BLUE | FOREGROUND_RED); + private static final short FOREGROUND_CYAN = (short) (FOREGROUND_BLUE | FOREGROUND_GREEN); + private static final short FOREGROUND_WHITE = (short) (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); + + private static final short BACKGROUND_BLACK = 0; + private static final short BACKGROUND_YELLOW = (short) (BACKGROUND_RED | BACKGROUND_GREEN); + private static final short BACKGROUND_MAGENTA = (short) (BACKGROUND_BLUE | BACKGROUND_RED); + private static final short BACKGROUND_CYAN = (short) (BACKGROUND_BLUE | BACKGROUND_GREEN); + private static final short BACKGROUND_WHITE = (short) (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE); + + private static final short[] ANSI_FOREGROUND_COLOR_MAP = { + FOREGROUND_BLACK, + FOREGROUND_RED, + FOREGROUND_GREEN, + FOREGROUND_YELLOW, + FOREGROUND_BLUE, + FOREGROUND_MAGENTA, + FOREGROUND_CYAN, + FOREGROUND_WHITE, + }; + + private static final short[] ANSI_BACKGROUND_COLOR_MAP = { + BACKGROUND_BLACK, + BACKGROUND_RED, + BACKGROUND_GREEN, + BACKGROUND_YELLOW, + BACKGROUND_BLUE, + BACKGROUND_MAGENTA, + BACKGROUND_CYAN, + BACKGROUND_WHITE, + }; + + private final CONSOLE_SCREEN_BUFFER_INFO info = new CONSOLE_SCREEN_BUFFER_INFO(); + private final short originalColors; + + private boolean negative; + private boolean bold; + private boolean underline; + private short savedX = -1; + private short savedY = -1; + + public WindowsAnsiWriter(Writer out) throws IOException { + super(out); + getConsoleInfo(); + originalColors = info.attributes(); + } + + private void getConsoleInfo() throws IOException { + out.flush(); + if (GetConsoleScreenBufferInfo(console, info) == 0) { + throw new IOException("Could not get the screen info: " + getLastErrorMessage()); + } + if (negative) { + info.attributes( invertAttributeColors(info.attributes()) ); + } + } + + private void applyAttribute() throws IOException { + out.flush(); + short attributes = info.attributes(); + // bold is simulated by high foreground intensity + if (bold) { + attributes |= FOREGROUND_INTENSITY; + } + // underline is simulated by high foreground intensity + if (underline) { + attributes |= BACKGROUND_INTENSITY; + } + if (negative) { + attributes = invertAttributeColors(attributes); + } + if (SetConsoleTextAttribute(console, attributes) == 0) { + throw new IOException(getLastErrorMessage()); + } + } + + private short invertAttributeColors(short attributes) { + // Swap the the Foreground and Background bits. + int fg = 0x000F & attributes; + fg <<= 4; + int bg = 0X00F0 & attributes; + bg >>= 4; + attributes = (short) ((attributes & 0xFF00) | fg | bg); + return attributes; + } + + private void applyCursorPosition() throws IOException { + info.cursorPosition().x( (short) Math.max(0, Math.min(info.size().x() - 1, info.cursorPosition().x())) ); + info.cursorPosition().y( (short) Math.max(0, Math.min(info.size().y() - 1, info.cursorPosition().y())) ); + if (SetConsoleCursorPosition(console, info.cursorPosition()) == 0) { + throw new IOException(getLastErrorMessage()); + } + } + + @Override + protected void processEraseScreen(int eraseOption) throws IOException { + getConsoleInfo(); + try (MemorySession session = MemorySession.openImplicit()) { + MemorySegment written = session.allocate( ValueLayout.JAVA_INT ); + switch (eraseOption) { + case ERASE_SCREEN: + COORD topLeft = new COORD( (short) 0, info.window().top() ); + int screenLength = info.window().height() * info.size().x(); + FillConsoleOutputAttribute(console, originalColors, screenLength, topLeft, written); + FillConsoleOutputCharacterW(console, ' ', screenLength, topLeft, written); + break; + case ERASE_SCREEN_TO_BEGINING: + COORD topLeft2 = new COORD( (short) 0, info.window().top() ); + int lengthToCursor = (info.cursorPosition().y() - info.window().top()) * info.size().x() + + info.cursorPosition().x(); + FillConsoleOutputAttribute(console, originalColors, lengthToCursor, topLeft2, written); + FillConsoleOutputCharacterW(console, ' ', lengthToCursor, topLeft2, written); + break; + case ERASE_SCREEN_TO_END: + int lengthToEnd = (info.window().bottom() - info.cursorPosition().y()) * info.size().x() + + (info.size().x() - info.cursorPosition().x()); + FillConsoleOutputAttribute(console, originalColors, lengthToEnd, info.cursorPosition(), written); + FillConsoleOutputCharacterW(console, ' ', lengthToEnd, info.cursorPosition(), written); + break; + default: + break; + } + } + } + + @Override + protected void processEraseLine(int eraseOption) throws IOException { + getConsoleInfo(); + try (MemorySession session = MemorySession.openImplicit()) { + MemorySegment written = session.allocate( ValueLayout.JAVA_INT ); + switch (eraseOption) { + case ERASE_LINE: + COORD leftColCurrRow = new COORD( (short) 0, info.cursorPosition().y() ); + FillConsoleOutputAttribute(console, originalColors, info.size().x(), leftColCurrRow, written); + FillConsoleOutputCharacterW(console, ' ', info.size().x(), leftColCurrRow, written); + break; + case ERASE_LINE_TO_BEGINING: + COORD leftColCurrRow2 = new COORD( (short) 0, info.cursorPosition().y() ); + FillConsoleOutputAttribute(console, originalColors, info.cursorPosition().x(), leftColCurrRow2, written); + FillConsoleOutputCharacterW(console, ' ', info.cursorPosition().x(), leftColCurrRow2, written); + break; + case ERASE_LINE_TO_END: + int lengthToLastCol = info.size().x() - info.cursorPosition().x(); + FillConsoleOutputAttribute(console, originalColors, lengthToLastCol, info.cursorPosition(), written); + FillConsoleOutputCharacterW(console, ' ', lengthToLastCol, info.cursorPosition(), written); + break; + default: + break; + } + } + } + + protected void processCursorUpLine(int count) throws IOException { + getConsoleInfo(); + info.cursorPosition().x( (short) 0 ); + info.cursorPosition().y( (short) (info.cursorPosition().y() - count) ); + applyCursorPosition(); + } + + protected void processCursorDownLine(int count) throws IOException { + getConsoleInfo(); + info.cursorPosition().x( (short) 0 ); + info.cursorPosition().y( (short) (info.cursorPosition().y() + count) ); + applyCursorPosition(); + } + + @Override + protected void processCursorLeft(int count) throws IOException { + getConsoleInfo(); + info.cursorPosition().x( (short) (info.cursorPosition().x() - count) ); + applyCursorPosition(); + } + + @Override + protected void processCursorRight(int count) throws IOException { + getConsoleInfo(); + info.cursorPosition().x( (short) (info.cursorPosition().x() + count) ); + applyCursorPosition(); + } + + @Override + protected void processCursorDown(int count) throws IOException { + getConsoleInfo(); + int nb = Math.max(0, info.cursorPosition().y() + count - info.size().y() + 1); + if (nb != count) { + info.cursorPosition().y( (short) (info.cursorPosition().y() + count) ); + applyCursorPosition(); + } + if (nb > 0) { + SMALL_RECT scroll = new SMALL_RECT( info.window() ); + scroll.top( (short) 0 ); + COORD org = new COORD(); + org.x( (short) 0 ); + org.y( (short)(- nb) ); + CHAR_INFO info = new CHAR_INFO( ' ', originalColors ); + ScrollConsoleScreenBuffer(console, scroll, scroll, org, info); + } + } + + @Override + protected void processCursorUp(int count) throws IOException { + getConsoleInfo(); + info.cursorPosition().y( (short) (info.cursorPosition().y() - count) ); + applyCursorPosition(); + } + + @Override + protected void processCursorTo(int row, int col) throws IOException { + getConsoleInfo(); + info.cursorPosition().y( (short) (info.window().top() + row - 1) ); + info.cursorPosition().x( (short) (col - 1) ); + applyCursorPosition(); + } + + @Override + protected void processCursorToColumn(int x) throws IOException { + getConsoleInfo(); + info.cursorPosition().x( (short) (x - 1) ); + applyCursorPosition(); + } + + @Override + protected void processSetForegroundColorExt(int paletteIndex) throws IOException { + int color = Colors.roundColor(paletteIndex, 16); + info.attributes( (short) ((info.attributes() & ~0x0007) | ANSI_FOREGROUND_COLOR_MAP[color & 0x07]) ); + info.attributes( (short) ((info.attributes() & ~FOREGROUND_INTENSITY) | (color >= 8 ? FOREGROUND_INTENSITY : 0)) ); + applyAttribute(); + } + + @Override + protected void processSetBackgroundColorExt(int paletteIndex) throws IOException { + int color = Colors.roundColor(paletteIndex, 16); + info.attributes( (short) ((info.attributes() & ~0x0070) | ANSI_BACKGROUND_COLOR_MAP[color & 0x07]) ); + info.attributes( (short) ((info.attributes() & ~BACKGROUND_INTENSITY) | (color >= 8 ? BACKGROUND_INTENSITY : 0)) ); + applyAttribute(); + } + + @Override + protected void processDefaultTextColor() throws IOException { + info.attributes( (short) ((info.attributes() & ~0x000F) | (originalColors & 0xF)) ); + info.attributes( (short) (info.attributes() & ~FOREGROUND_INTENSITY) ); + applyAttribute(); + } + + @Override + protected void processDefaultBackgroundColor() throws IOException { + info.attributes( (short) ((info.attributes() & ~0x00F0) | (originalColors & 0xF0)) ); + info.attributes( (short) (info.attributes() & ~BACKGROUND_INTENSITY) ); + applyAttribute(); + } + + @Override + protected void processAttributeRest() throws IOException { + info.attributes( (short) ((info.attributes() & ~0x00FF) | originalColors) ); + this.negative = false; + this.bold = false; + this.underline = false; + applyAttribute(); + } + + @Override + protected void processSetAttribute(int attribute) throws IOException { + switch (attribute) { + case ATTRIBUTE_INTENSITY_BOLD: + bold = true; + applyAttribute(); + break; + case ATTRIBUTE_INTENSITY_NORMAL: + bold = false; + applyAttribute(); + break; + + case ATTRIBUTE_UNDERLINE: + underline = true; + applyAttribute(); + break; + case ATTRIBUTE_UNDERLINE_OFF: + underline = false; + applyAttribute(); + break; + + case ATTRIBUTE_NEGATIVE_ON: + negative = true; + applyAttribute(); + break; + case ATTRIBUTE_NEGATIVE_OFF: + negative = false; + applyAttribute(); + break; + default: + break; + } + } + + @Override + protected void processSaveCursorPosition() throws IOException { + getConsoleInfo(); + savedX = info.cursorPosition().x(); + savedY = info.cursorPosition().y(); + } + + @Override + protected void processRestoreCursorPosition() throws IOException { + // restore only if there was a save operation first + if (savedX != -1 && savedY != -1) { + out.flush(); + info.cursorPosition().x( savedX ); + info.cursorPosition().y( savedY ); + applyCursorPosition(); + } + } + + @Override + protected void processInsertLine(int optionInt) throws IOException { + getConsoleInfo(); + SMALL_RECT scroll = info.window().copy(); + scroll.top( info.cursorPosition().y() ); + COORD org = new COORD( (short) 0, (short)(info.cursorPosition().y() + optionInt) ); + CHAR_INFO info = new CHAR_INFO( ' ', originalColors ); + if (ScrollConsoleScreenBuffer(console, scroll, scroll, org, info) == 0) { + throw new IOException(getLastErrorMessage()); + } + } + + @Override + protected void processDeleteLine(int optionInt) throws IOException { + getConsoleInfo(); + SMALL_RECT scroll = info.window().copy(); + scroll.top( info.cursorPosition().y() ); + COORD org = new COORD( (short) 0, (short)(info.cursorPosition().y() - optionInt) ); + CHAR_INFO info = new CHAR_INFO( ' ', originalColors ); + if (ScrollConsoleScreenBuffer(console, scroll, scroll, org, info) == 0) { + throw new IOException(getLastErrorMessage()); + } + } + + @Override + protected void processChangeWindowTitle(String title) { + try ( MemorySession session = MemorySession.openImplicit() ) + { + MemorySegment str = session.allocateUtf8String( title ); + SetConsoleTitleW( str ); + } + } +}