News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests

Main Menu

Win32 Console Addition

Started by demondoido, April 09, 2013, 01:56:04 AM

Previous topic - Next topic

dedndave

 :biggrin:

long ago, i reported some buggish behaviour of the console window
http://www.masmforum.com/board/index.php?topic=11927.msg90572#msg90572

using the CRT input functions seems to help those problems a little, but not completely
at one time, i debugged into the CRT getch function - it is a very involved routine - lol
the guys at ms have been working on improving the CRT for a long time
so - it's probably as good as it gets

even so, i am always interested in learning more about the specifics of console behaviour
but, if i want something to work in a half-way predictable manner, i write a GUI app   8)

dedndave

wellllll
i have finally finished analyzing the edit keys - lol

along the way, i discovered a little undocumented fact of line edit modes with ReadConsole and ReadFile
these functions only allow a line length of 254 characters, plus the enter key
so - the simple way to use them is to use a buffer length of 256 chars  :t

still, that doesn't allow you to limit the line length to, say, 10 characters or something

qWord

Years back I've written the following function, which limits the input by processing the console events retuned by ReadConsoleInput():
;__UNICODE__ EQU 1
include \masm32\include\masm32rt.inc
.686

TCCHR macro lbl,args:VARARG
    IFDEF __UNICODE__
        UCCSTR lbl,args
    ELSE
        ?cstr? lbl,args
    ENDIF
endm

ReadConsoleTimeOut proto psz:ptr TCHAR,cc:DWORD,dwTimeOut:DWORD
IsValidTChar proto TChar:DWORD
TStrCopyN proto pDest:ptr TCHAR,ccDest:DWORD,pszSrc:ptr TCHAR,ccSrc:DWORD
TStrCat proto pBuffer:DWORD,ccBuffer:DWORD,psz1:DWORD,psz2:DWORD

.code

main proc
LOCAL sz[256]:TCHAR
   
    print "enter up to 10 characters: "
    invoke ReadConsoleTimeOut,ADDR sz,11,INFINITE
    print "your input: ",%'<'
    print ADDR sz,%'>'
    print chr$(13,10)
   
    print "enter up to 128 characters: "
    invoke ReadConsoleTimeOut,ADDR sz,129,INFINITE
    print ADDR sz,13,10
    print "your input: ",%'<'
    print ADDR sz,%'>'
    print chr$(13,10)

    inkey
    exit
main endp

ReadConsoleTimeOut proc uses edi esi ebx psz:ptr TCHAR,cc:DWORD,dwTimeOut:DWORD
LOCAL hStdIn:HANDLE
LOCAL hStdOut:HANDLE
LOCAL pBuffer:ptr TCHAR
LOCAL nEvents:DWORD
LOCAL ticks:DWORD
LOCAL csbi:CONSOLE_SCREEN_BUFFER_INFO
LOCAL ir[10]:INPUT_RECORD

    mov eax,psz
    mov TCHAR ptr [eax],0
    .if !cc
        ret
    .endif
   
    mov edx,cc
    lea edx,[edx*TCHAR]
    .if !rvx(pBuffer = GlobalAlloc,GPTR,edx)
        ret
    .endif
    mov hStdIn,rv(GetStdHandle,STD_INPUT_HANDLE)
    mov hStdOut,rv(GetStdHandle,STD_OUTPUT_HANDLE)
    .if dwTimeOut != INFINITE
        mov ticks,rv(GetTickCount)
    .endif

    xor edi,edi
    xor esi,esi
    .while 1
        .break .if rv(WaitForSingleObject,hStdIn,dwTimeOut) == WAIT_FAILED || eax == WAIT_TIMEOUT
       
        .if dwTimeOut != INFINITE
            mov edx,rv(GetTickCount)
            sub edx,ticks
            .break .if edx > dwTimeOut
            sub dwTimeOut,edx
            mov ticks,eax
        .endif
       
        lea ebx,ir
        assume ebx: ptr INPUT_RECORD
        .break .if !rv(ReadConsoleInput,hStdIn,ebx,LENGTHOF ir,&nEvents)
        .while nEvents != 0
            .if [ebx].EventType == KEY_EVENT && [ebx].KeyEvent.bKeyDown
                fn GetConsoleScreenBufferInfo,hStdOut,&csbi
                movzx edx,[ebx].KeyEvent.wVirtualKeyCode
                .if edx == VK_RETURN || edx == VK_ESCAPE
                    jmp _exit
                .elseif edx == VK_BACK
            @@: .if edi > 0
                        .if csbi.dwCursorPosition.x > 0
                            sub csbi.dwCursorPosition.x,1
                        .else
                            .if csbi.dwCursorPosition.y > 0
                                sub csbi.dwCursorPosition.y,1
                                movzx eax,csbi.dwSize.x
                                sub eax,1
                                mov csbi.dwCursorPosition.x,ax
                            .endif
                        .endif
                        invoke SetConsoleCursorPosition,hStdOut,DWORD ptr csbi.dwCursorPosition
                        dec edi
                        jmp @F
                    .endif
                .elseif edx == VK_DELETE
            @@: .if edi < esi
                        mov edx,psz
                        lea edx,[edx+edi*TCHAR]
                        mov TCHAR ptr [edx],0
                        lea eax,[edx+TCHAR]
                        invoke TStrCat,psz,cc,psz,eax
                        mov ecx,eax
                        sub ecx,edi
                        invoke WriteConsole,hStdOut,edx,ecx,0,0
                        fn WriteConsole,hStdOut," ",1,0,0
                        invoke SetConsoleCursorPosition,hStdOut,DWORD ptr csbi.dwCursorPosition
                        sub esi,1
                    .endif
                .elseif edx == VK_LEFT
                    .if edi > 0
                        .if csbi.dwCursorPosition.x > 0
                            sub csbi.dwCursorPosition.x,1
                        .else
                            .if csbi.dwCursorPosition.y > 0
                                sub csbi.dwCursorPosition.y,1
                                mov ax,csbi.dwSize.x
                                dec ax
                                mov csbi.dwCursorPosition.x,ax
                            .endif
                        .endif
                        invoke SetConsoleCursorPosition,hStdOut,DWORD ptr csbi.dwCursorPosition
                        sub edi,1
                    .endif
                .elseif edx == VK_RIGHT
                    .if edi < esi
                        movzx eax,csbi.dwSize.x
                        sub eax,1
                        .if csbi.dwCursorPosition.x >= ax
                            mov csbi.dwCursorPosition.x,0
                            add csbi.dwCursorPosition.y,1
                        .else
                            add csbi.dwCursorPosition.x,1
                        .endif
                        invoke SetConsoleCursorPosition,hStdOut,DWORD ptr csbi.dwCursorPosition
                        add edi,1
                    .endif
                .else
                    lea edx,[esi+1]
                    movzx ecx,[ebx].KeyEvent.UnicodeChar
                    .if edx < cc && rv(IsValidTChar,ecx)
                        mov edx,psz
                        lea eax,[edx+edi*TCHAR]
                        invoke TStrCopyN,pBuffer,cc,eax,-1
                       
                        movzx ecx,TCHAR ptr [ebx].KeyEvent.UnicodeChar
                        mov WORD ptr [edx+edi*TCHAR],cx
                        mov WORD ptr [edx+edi*TCHAR+TCHAR],0
                        mov esi,rv(TStrCat,psz,cc,psz,pBuffer)
                        sub eax,edi
                        lea edx,[edx+edi*TCHAR]
                        invoke WriteConsole,hStdOut,edx,eax,0,0
                        add edi,1
                       
                        movzx edx,csbi.dwSize.x
                        sub edx,1
                        .if csbi.dwCursorPosition.x >= dx
                            mov csbi.dwCursorPosition.x,0
                            add csbi.dwCursorPosition.y,1
                        .else
                            add csbi.dwCursorPosition.x,1
                        .endif
                        invoke SetConsoleCursorPosition,hStdOut,DWORD ptr csbi.dwCursorPosition
                    .endif
                .endif
            .endif
            add ebx,INPUT_RECORD
            dec nEvents
        .endw
    .endw

_exit:
    invoke GlobalFree,pBuffer
    fn WriteConsole,hStdOut,chr$(13,10),2,0,0
   
    mov eax,esi
    ret

ReadConsoleTimeOut endp

; OUT: valid if eax > 0
IsValidTChar proc uses edx ecx TChar:DWORD

    .const
        TCCHR ValidCharT," \t.,:;\x-_#'\q+-*/\\?{}\a\b[]&\p$\r\l|^~ยด`=@"
    .code
   
    .if rv(IsCharAlphaNumeric,TChar)
        ret
    .endif
    and TChar,0ffffh
    mov edx,OFFSET ValidCharT
    movzx eax,TCHAR ptr [edx]
    .while eax != 0
        lea edx,[edx+TCHAR]
        .break .if eax == TChar
        movzx eax,TCHAR ptr [edx]
    .endw
    ret

IsValidTChar endp

TStrCopyN proc uses ecx edx ebx edi esi pDest:ptr TCHAR,ccDest:DWORD,pszSrc:ptr TCHAR,ccSrc:DWORD
LOCAL cc:DWORD

    mov edx,pDest
    mov ecx,ccDest
    mov eax,pszSrc
    mov cc,0
    .repeat
        sub ecx,1 ; for termination zero
        .break .if ZERO?
        test ccSrc,-1
        .break .if ZERO?
        .repeat
            movzx ebx,TCHAR ptr [eax]
            test ebx,ebx
            .break .if ZERO?
            IFDEF __UNICODE__
                mov TCHAR ptr [edx],bx
            ELSE
                mov TCHAR ptr [edx],bl
            ENDIF
            lea eax,[eax+TCHAR]
            lea edx,[edx+TCHAR]
            add cc,1
            sub ccSrc,1
            .break .if ZERO?
            sub ecx,1
        .until ZERO?
    .until 1
    mov TCHAR ptr [edx],0
    mov eax,cc
    ret
   
TStrCopyN endp

TStrCat proc uses ecx edx ebx edi esi pBuffer:DWORD,ccBuffer:DWORD,psz1:DWORD,psz2:DWORD
   
    mov edi,pBuffer
    mov ecx,ccBuffer
    mov esi,psz1
    mov edx,psz2
    mov eax,1
    mov ccBuffer,0
   
    sub ecx,1 ; for termination-zero
    jz _exit
   
_begin:
    movzx ebx,TCHAR ptr [esi]
    test ebx,ebx
    jz @F
    IFDEF __UNICODE__
        mov TCHAR ptr [edi],bx
    ELSE
        mov TCHAR ptr [edi],bl
    ENDIF
    lea edi,[edi+TCHAR]
    lea esi,[esi+TCHAR]
    inc ccBuffer
    sub ecx,1
    jz _exit
    jmp _begin
@@:
    test eax,eax
    cmovnz eax,ebx
    cmovnz esi,edx
    jnz _begin
_exit:
    mov TCHAR ptr [edi],0
   
    mov eax,ccBuffer
    ret

TStrCat endp
end main

MREAL macros - when you need floating point arithmetic while assembling!

dedndave

interesting code, qWord
i have something similar in mind, but i had no intention of providing a timeout - lol

the basic form i am using looks like this...
    .repeat
        .repeat
            INVOKE  ReadConsoleInput,hStdInp,addr ir,1,addr uRecCnt
            movzx   edx,word ptr ir.EventType
        .until (edx==KEY_EVENT) && (edx==ir.KeyEvent.bKeyDown)
        movzx   eax,word ptr ir.KeyEvent.UnicodeChar
        mov     ecx,ir.KeyEvent.dwControlKeyState
        movzx   edx,word ptr ir.KeyEvent.wVirtualKeyCode

;       .if
;       .elseif
;       .elseif                     handle line input and edit keys here
;       .elseif
;       .endif

    .until (ax==VK_RETURN) && !(cx&(LEFT_CTRL_PRESSED or RIGHT_CTRL_PRESSED or LEFT_ALT_PRESSED or RIGHT_ALT_PRESSED))

qWord

Quote from: dedndave on April 13, 2013, 12:44:12 AMbut i had no intention of providing a timeout
removing that few extra lines should be no problem for anyone  ;)
MREAL macros - when you need floating point arithmetic while assembling!

dedndave

you know me - i like to write my own stuff   :P
i am not much of a script kiddie

here are some operations i found - couple of them i wasn't aware of

line edit supports these keys:
backspace, tab, enter, insert, delete, home, end, left/right cursor

combinations:
ctrl left/right arrow - moves cursor to first/last character
ctrl home - removes all characters preceeding the cursor
ctrl end - removes all characters from cursor to end of line

i didn't play with all the key combinations - there might be an "undo" somewhere   :P
or - there could be

jj2007

Quote from: dedndave on April 13, 2013, 02:28:48 AM
i didn't play with all the key combinations - there might be an "undo" somewhere   :P
or - there could be

Ctrl Z doesn't work, and SetConsoleCtrlHandler concerns only special keys like Ctrl C and Ctrl Break. If you are really keen on undo, handle Ctrl Break...

dedndave

ctrl-z is normally used to generate an EOF (SUB char)

ctrl-c and ctrl-break should exit/abort
i can make that happen, if i want to
but, my thinking was that the guy might have some ctrl handler in place to handle log off or shut down

i doubt there is an undo in the console - lol
but, we could add our own
i was thinking maybe one of the function keys, perhaps with ctrl or alt

dedndave

in this first set of routines, i use a ConIn256 routine that assumes a buffer length of 256 TCHAR's
that makes it simple
in the attachment are both ANSI and UNICODE builds

any problem reports appreciated   :P