2013-01-02 16:36:00 +01:00
|
|
|
/*
|
2020-08-24 13:21:06 +02:00
|
|
|
* Copyright 1998 Alexandre Julliard
|
|
|
|
* Copyright 2001 Eric Pouech
|
2013-01-02 16:36:00 +01:00
|
|
|
* Copyright 2012 Detlef Riekenberg
|
2020-08-24 13:21:06 +02:00
|
|
|
* Copyright 2020 Jacek Caban
|
2013-01-02 16:36:00 +01:00
|
|
|
*
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This library is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with this library; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
|
|
|
*/
|
|
|
|
|
2020-08-20 23:49:46 +02:00
|
|
|
#include <stdarg.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <ntstatus.h>
|
|
|
|
#define WIN32_NO_STATUS
|
|
|
|
#include <windef.h>
|
|
|
|
#include <winbase.h>
|
|
|
|
#include <winuser.h>
|
2020-08-24 13:21:06 +02:00
|
|
|
#include <winnls.h>
|
2020-08-20 23:49:46 +02:00
|
|
|
#include <winternl.h>
|
|
|
|
|
2020-08-20 23:51:17 +02:00
|
|
|
#include "wine/condrv.h"
|
2020-08-20 23:51:05 +02:00
|
|
|
#include "wine/server.h"
|
2013-01-02 16:36:00 +01:00
|
|
|
#include "wine/debug.h"
|
|
|
|
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(conhost);
|
|
|
|
|
2020-08-24 13:21:26 +02:00
|
|
|
struct history_line
|
|
|
|
{
|
|
|
|
size_t len;
|
|
|
|
WCHAR text[1];
|
|
|
|
};
|
|
|
|
|
2020-08-20 23:49:46 +02:00
|
|
|
struct console
|
2013-01-02 16:36:00 +01:00
|
|
|
{
|
2020-08-20 23:49:46 +02:00
|
|
|
HANDLE server; /* console server handle */
|
2020-08-20 23:51:17 +02:00
|
|
|
unsigned int mode; /* input mode */
|
2020-08-25 14:52:25 +02:00
|
|
|
INPUT_RECORD *records; /* input records */
|
|
|
|
unsigned int record_count; /* number of input records */
|
|
|
|
unsigned int record_size; /* size of input records buffer */
|
2020-08-24 13:21:43 +02:00
|
|
|
WCHAR *title; /* console title */
|
|
|
|
size_t title_len; /* length of console title */
|
2020-08-24 13:21:26 +02:00
|
|
|
struct history_line **history; /* lines history */
|
2020-08-24 13:21:06 +02:00
|
|
|
unsigned int history_size; /* number of entries in history array */
|
|
|
|
unsigned int history_index; /* number of used entries in history array */
|
|
|
|
unsigned int history_mode; /* mode of history (non zero means remove doubled strings */
|
|
|
|
unsigned int edition_mode; /* index to edition mode flavors */
|
|
|
|
unsigned int input_cp; /* console input codepage */
|
|
|
|
unsigned int output_cp; /* console output codepage */
|
|
|
|
unsigned int win; /* window handle if backend supports it */
|
2020-08-20 23:49:46 +02:00
|
|
|
};
|
|
|
|
|
2020-08-20 23:51:05 +02:00
|
|
|
static void *ioctl_buffer;
|
|
|
|
static size_t ioctl_buffer_size;
|
|
|
|
|
|
|
|
static void *alloc_ioctl_buffer( size_t size )
|
|
|
|
{
|
|
|
|
if (size > ioctl_buffer_size)
|
|
|
|
{
|
|
|
|
void *new_buffer;
|
|
|
|
if (!(new_buffer = realloc( ioctl_buffer, size ))) return NULL;
|
|
|
|
ioctl_buffer = new_buffer;
|
|
|
|
ioctl_buffer_size = size;
|
|
|
|
}
|
|
|
|
return ioctl_buffer;
|
|
|
|
}
|
|
|
|
|
2020-08-25 14:52:25 +02:00
|
|
|
/* add input events to a console input queue */
|
|
|
|
static NTSTATUS write_console_input( struct console *console, const INPUT_RECORD *records,
|
|
|
|
unsigned int count )
|
|
|
|
{
|
|
|
|
TRACE( "%u\n", count );
|
|
|
|
|
|
|
|
if (!count) return STATUS_SUCCESS;
|
|
|
|
if (console->record_count + count > console->record_size)
|
|
|
|
{
|
|
|
|
INPUT_RECORD *new_rec;
|
|
|
|
if (!(new_rec = realloc( console->records, (console->record_size * 2 + count) * sizeof(INPUT_RECORD) )))
|
|
|
|
return STATUS_NO_MEMORY;
|
|
|
|
console->records = new_rec;
|
|
|
|
console->record_size = console->record_size * 2 + count;
|
|
|
|
}
|
|
|
|
memcpy( console->records + console->record_count, records, count * sizeof(INPUT_RECORD) );
|
|
|
|
|
|
|
|
if (console->mode & ENABLE_PROCESSED_INPUT)
|
|
|
|
{
|
|
|
|
unsigned int i = 0;
|
|
|
|
while (i < count)
|
|
|
|
{
|
|
|
|
if (records[i].EventType == KEY_EVENT &&
|
|
|
|
records[i].Event.KeyEvent.uChar.UnicodeChar == 'C' - 64 &&
|
|
|
|
!(records[i].Event.KeyEvent.dwControlKeyState & ENHANCED_KEY))
|
|
|
|
{
|
|
|
|
if (i != count - 1)
|
|
|
|
memcpy( &console->records[console->record_count + i],
|
|
|
|
&console->records[console->record_count + i + 1],
|
|
|
|
(count - i - 1) * sizeof(INPUT_RECORD) );
|
|
|
|
count--;
|
|
|
|
if (records[i].Event.KeyEvent.bKeyDown)
|
|
|
|
{
|
|
|
|
FIXME("CTRL C\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else i++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
console->record_count += count;
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
|
2020-08-24 13:21:56 +02:00
|
|
|
static NTSTATUS set_console_title( struct console *console, const WCHAR *in_title, size_t size )
|
|
|
|
{
|
|
|
|
WCHAR *title = NULL;
|
|
|
|
|
|
|
|
TRACE( "%s\n", debugstr_wn(in_title, size) );
|
|
|
|
|
|
|
|
if (size)
|
|
|
|
{
|
|
|
|
if (!(title = malloc( size ))) return STATUS_NO_MEMORY;
|
|
|
|
memcpy( title, in_title, size );
|
|
|
|
}
|
|
|
|
free( console->title );
|
|
|
|
console->title = title;
|
|
|
|
console->title_len = size;
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
|
2020-08-20 23:51:05 +02:00
|
|
|
static NTSTATUS console_input_ioctl( struct console *console, unsigned int code, const void *in_data,
|
|
|
|
size_t in_size, size_t *out_size )
|
|
|
|
{
|
2020-08-20 23:51:17 +02:00
|
|
|
switch (code)
|
|
|
|
{
|
|
|
|
case IOCTL_CONDRV_GET_MODE:
|
|
|
|
{
|
|
|
|
DWORD *mode;
|
|
|
|
TRACE( "returning mode %x\n", console->mode );
|
|
|
|
if (in_size || *out_size != sizeof(*mode)) return STATUS_INVALID_PARAMETER;
|
|
|
|
if (!(mode = alloc_ioctl_buffer( *out_size ))) return STATUS_NO_MEMORY;
|
|
|
|
*mode = console->mode;
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
|
2020-08-24 13:20:53 +02:00
|
|
|
case IOCTL_CONDRV_SET_MODE:
|
|
|
|
if (in_size != sizeof(unsigned int) || *out_size) return STATUS_INVALID_PARAMETER;
|
|
|
|
console->mode = *(unsigned int *)in_data;
|
|
|
|
TRACE( "set %x mode\n", console->mode );
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
|
2020-08-25 14:52:25 +02:00
|
|
|
case IOCTL_CONDRV_WRITE_INPUT:
|
|
|
|
if (in_size % sizeof(INPUT_RECORD) || *out_size) return STATUS_INVALID_PARAMETER;
|
|
|
|
return write_console_input( console, in_data, in_size / sizeof(INPUT_RECORD) );
|
|
|
|
|
2020-08-24 13:21:06 +02:00
|
|
|
case IOCTL_CONDRV_GET_INPUT_INFO:
|
|
|
|
{
|
|
|
|
struct condrv_input_info *info;
|
|
|
|
TRACE( "get info\n" );
|
|
|
|
if (in_size || *out_size != sizeof(*info)) return STATUS_INVALID_PARAMETER;
|
|
|
|
if (!(info = alloc_ioctl_buffer( sizeof(*info )))) return STATUS_NO_MEMORY;
|
|
|
|
info->history_mode = console->history_mode;
|
|
|
|
info->history_size = console->history_size;
|
|
|
|
info->history_index = console->history_index;
|
|
|
|
info->edition_mode = console->edition_mode;
|
|
|
|
info->input_cp = console->input_cp;
|
|
|
|
info->output_cp = console->output_cp;
|
|
|
|
info->win = console->win;
|
2020-08-25 14:52:25 +02:00
|
|
|
info->input_count = console->record_count;
|
2020-08-24 13:21:06 +02:00
|
|
|
return STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
|
2020-08-24 13:21:26 +02:00
|
|
|
case IOCTL_CONDRV_SET_INPUT_INFO:
|
|
|
|
{
|
|
|
|
const struct condrv_input_info_params *params = in_data;
|
|
|
|
TRACE( "set info\n" );
|
|
|
|
if (in_size != sizeof(*params) || *out_size) return STATUS_INVALID_PARAMETER;
|
|
|
|
if (params->mask & SET_CONSOLE_INPUT_INFO_HISTORY_MODE)
|
|
|
|
{
|
|
|
|
console->history_mode = params->info.history_mode;
|
|
|
|
}
|
|
|
|
if ((params->mask & SET_CONSOLE_INPUT_INFO_HISTORY_SIZE) &&
|
|
|
|
console->history_size != params->info.history_size)
|
|
|
|
{
|
|
|
|
struct history_line **mem = NULL;
|
|
|
|
int i, delta;
|
|
|
|
|
|
|
|
if (params->info.history_size)
|
|
|
|
{
|
|
|
|
if (!(mem = malloc( params->info.history_size * sizeof(*mem) )))
|
|
|
|
return STATUS_NO_MEMORY;
|
|
|
|
memset( mem, 0, params->info.history_size * sizeof(*mem) );
|
|
|
|
}
|
|
|
|
|
|
|
|
delta = (console->history_index > params->info.history_size)
|
|
|
|
? (console->history_index - params->info.history_size) : 0;
|
|
|
|
|
|
|
|
for (i = delta; i < console->history_index; i++)
|
|
|
|
{
|
|
|
|
mem[i - delta] = console->history[i];
|
|
|
|
console->history[i] = NULL;
|
|
|
|
}
|
|
|
|
console->history_index -= delta;
|
|
|
|
|
|
|
|
for (i = 0; i < console->history_size; i++)
|
|
|
|
free( console->history[i] );
|
|
|
|
free( console->history );
|
|
|
|
console->history = mem;
|
|
|
|
console->history_size = params->info.history_size;
|
|
|
|
}
|
|
|
|
if (params->mask & SET_CONSOLE_INPUT_INFO_EDITION_MODE)
|
|
|
|
{
|
|
|
|
console->edition_mode = params->info.edition_mode;
|
|
|
|
}
|
|
|
|
if (params->mask & SET_CONSOLE_INPUT_INFO_INPUT_CODEPAGE)
|
|
|
|
{
|
|
|
|
console->input_cp = params->info.input_cp;
|
|
|
|
}
|
|
|
|
if (params->mask & SET_CONSOLE_INPUT_INFO_OUTPUT_CODEPAGE)
|
|
|
|
{
|
|
|
|
console->output_cp = params->info.output_cp;
|
|
|
|
}
|
|
|
|
if (params->mask & SET_CONSOLE_INPUT_INFO_WIN)
|
|
|
|
{
|
|
|
|
console->win = params->info.win;
|
|
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
|
2020-08-24 13:21:43 +02:00
|
|
|
case IOCTL_CONDRV_GET_TITLE:
|
|
|
|
{
|
|
|
|
WCHAR *result;
|
|
|
|
if (in_size) return STATUS_INVALID_PARAMETER;
|
|
|
|
TRACE( "returning title %s\n", debugstr_wn(console->title,
|
|
|
|
console->title_len / sizeof(WCHAR)) );
|
|
|
|
if (!(result = alloc_ioctl_buffer( *out_size = min( *out_size, console->title_len ))))
|
|
|
|
return STATUS_NO_MEMORY;
|
|
|
|
if (*out_size) memcpy( result, console->title, *out_size );
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
|
2020-08-24 13:21:56 +02:00
|
|
|
case IOCTL_CONDRV_SET_TITLE:
|
|
|
|
if (in_size % sizeof(WCHAR) || *out_size) return STATUS_INVALID_PARAMETER;
|
|
|
|
return set_console_title( console, in_data, in_size );
|
|
|
|
|
2020-08-20 23:51:17 +02:00
|
|
|
default:
|
|
|
|
FIXME( "unsupported ioctl %x\n", code );
|
|
|
|
return STATUS_NOT_SUPPORTED;
|
|
|
|
}
|
2020-08-20 23:51:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static NTSTATUS process_console_ioctls( struct console *console )
|
|
|
|
{
|
|
|
|
size_t out_size = 0, in_size;
|
|
|
|
unsigned int code;
|
|
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
|
|
|
|
|
|
for (;;)
|
|
|
|
{
|
|
|
|
if (status) out_size = 0;
|
|
|
|
|
|
|
|
SERVER_START_REQ( get_next_console_request )
|
|
|
|
{
|
|
|
|
req->handle = wine_server_obj_handle( console->server );
|
|
|
|
req->status = status;
|
2020-08-25 14:52:25 +02:00
|
|
|
req->signal = console->record_count != 0;
|
2020-08-20 23:51:05 +02:00
|
|
|
wine_server_add_data( req, ioctl_buffer, out_size );
|
|
|
|
wine_server_set_reply( req, ioctl_buffer, ioctl_buffer_size );
|
|
|
|
status = wine_server_call( req );
|
|
|
|
code = reply->code;
|
|
|
|
out_size = reply->out_size;
|
|
|
|
in_size = wine_server_reply_size( reply );
|
|
|
|
}
|
|
|
|
SERVER_END_REQ;
|
|
|
|
|
|
|
|
if (status == STATUS_PENDING) return STATUS_SUCCESS;
|
|
|
|
if (status == STATUS_BUFFER_OVERFLOW)
|
|
|
|
{
|
|
|
|
if (!alloc_ioctl_buffer( out_size )) return STATUS_NO_MEMORY;
|
|
|
|
status = STATUS_SUCCESS;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (status)
|
|
|
|
{
|
|
|
|
TRACE( "failed to get next request: %#x\n", status );
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
status = console_input_ioctl( console, code, ioctl_buffer, in_size, &out_size );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-20 23:49:46 +02:00
|
|
|
static int main_loop( struct console *console, HANDLE signal )
|
|
|
|
{
|
|
|
|
HANDLE signal_event = NULL;
|
|
|
|
HANDLE wait_handles[2];
|
|
|
|
unsigned int wait_cnt = 0;
|
|
|
|
unsigned short signal_id;
|
|
|
|
IO_STATUS_BLOCK signal_io;
|
|
|
|
NTSTATUS status;
|
|
|
|
DWORD res;
|
|
|
|
|
|
|
|
if (signal)
|
|
|
|
{
|
|
|
|
if (!(signal_event = CreateEventW( NULL, TRUE, FALSE, NULL ))) return 1;
|
|
|
|
status = NtReadFile( signal, signal_event, NULL, NULL, &signal_io, &signal_id,
|
|
|
|
sizeof(signal_id), NULL, NULL );
|
|
|
|
if (status && status != STATUS_PENDING) return 1;
|
|
|
|
}
|
2013-01-02 16:36:00 +01:00
|
|
|
|
2020-08-20 23:51:05 +02:00
|
|
|
if (!alloc_ioctl_buffer( 4096 )) return 1;
|
|
|
|
|
2020-08-20 23:49:46 +02:00
|
|
|
wait_handles[wait_cnt++] = console->server;
|
|
|
|
if (signal) wait_handles[wait_cnt++] = signal_event;
|
|
|
|
|
|
|
|
for (;;)
|
|
|
|
{
|
|
|
|
res = WaitForMultipleObjects( wait_cnt, wait_handles, FALSE, INFINITE );
|
|
|
|
|
|
|
|
switch (res)
|
|
|
|
{
|
|
|
|
case WAIT_OBJECT_0:
|
2020-08-20 23:51:05 +02:00
|
|
|
if (process_console_ioctls( console )) return 0;
|
|
|
|
break;
|
2020-08-20 23:49:46 +02:00
|
|
|
|
|
|
|
case WAIT_OBJECT_0 + 1:
|
|
|
|
if (signal_io.Status || signal_io.Information != sizeof(signal_id))
|
|
|
|
{
|
|
|
|
TRACE( "signaled quit\n" );
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
FIXME( "unimplemented signal %x\n", signal_id );
|
|
|
|
status = NtReadFile( signal, signal_event, NULL, NULL, &signal_io, &signal_id,
|
|
|
|
sizeof(signal_id), NULL, NULL );
|
|
|
|
if (status && status != STATUS_PENDING) return 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
TRACE( "wait failed, quit\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
2013-01-02 16:36:00 +01:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2020-08-20 23:49:46 +02:00
|
|
|
|
|
|
|
int __cdecl wmain(int argc, WCHAR *argv[])
|
|
|
|
{
|
|
|
|
int headless = 0, i, width = 80, height = 150;
|
|
|
|
HANDLE signal = NULL;
|
|
|
|
WCHAR *end;
|
|
|
|
|
|
|
|
static struct console console;
|
|
|
|
|
|
|
|
for (i = 0; i < argc; i++) TRACE("%s ", wine_dbgstr_w(argv[i]));
|
|
|
|
TRACE("\n");
|
|
|
|
|
2020-08-20 23:51:17 +02:00
|
|
|
console.mode = ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT |
|
|
|
|
ENABLE_ECHO_INPUT | ENABLE_MOUSE_INPUT | ENABLE_INSERT_MODE |
|
|
|
|
ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS | ENABLE_AUTO_POSITION;
|
2020-08-24 13:21:06 +02:00
|
|
|
console.input_cp = console.output_cp = GetOEMCP();
|
|
|
|
console.history_size = 50;
|
2020-08-24 13:21:26 +02:00
|
|
|
if (!(console.history = calloc( console.history_size, sizeof(*console.history) ))) return 1;
|
2020-08-20 23:51:17 +02:00
|
|
|
|
2020-08-20 23:49:46 +02:00
|
|
|
for (i = 1; i < argc; i++)
|
|
|
|
{
|
|
|
|
if (!wcscmp( argv[i], L"--headless"))
|
|
|
|
{
|
|
|
|
headless = 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!wcscmp( argv[i], L"--width" ))
|
|
|
|
{
|
|
|
|
if (++i == argc) return 1;
|
|
|
|
width = wcstol( argv[i], &end, 0 );
|
|
|
|
if (!width || width > 0xffff || *end) return 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!wcscmp( argv[i], L"--height" ))
|
|
|
|
{
|
|
|
|
if (++i == argc) return 1;
|
|
|
|
height = wcstol( argv[i], &end, 0 );
|
|
|
|
if (!height || height > 0xffff || *end) return 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!wcscmp( argv[i], L"--signal" ))
|
|
|
|
{
|
|
|
|
if (++i == argc) return 1;
|
|
|
|
signal = ULongToHandle( wcstol( argv[i], &end, 0 ));
|
|
|
|
if (*end) return 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!wcscmp( argv[i], L"--server" ))
|
|
|
|
{
|
|
|
|
if (++i == argc) return 1;
|
|
|
|
console.server = ULongToHandle( wcstol( argv[i], &end, 0 ));
|
|
|
|
if (*end) return 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
FIXME( "unknown option %s\n", debugstr_w(argv[i]) );
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!headless)
|
|
|
|
{
|
|
|
|
FIXME( "windowed mode not supported\n" );
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!console.server)
|
|
|
|
{
|
|
|
|
ERR( "no server handle\n" );
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return main_loop( &console, signal );
|
|
|
|
}
|