diff options
Diffstat (limited to 'erts/emulator/drivers/win32')
-rw-r--r-- | erts/emulator/drivers/win32/mem_drv.c | 141 | ||||
-rw-r--r-- | erts/emulator/drivers/win32/registry_drv.c | 535 | ||||
-rw-r--r-- | erts/emulator/drivers/win32/ttsl_drv.c | 751 | ||||
-rw-r--r-- | erts/emulator/drivers/win32/win_con.c | 2259 | ||||
-rw-r--r-- | erts/emulator/drivers/win32/win_con.h | 39 | ||||
-rw-r--r-- | erts/emulator/drivers/win32/win_efile.c | 1426 | ||||
-rw-r--r-- | erts/emulator/drivers/win32/winsock_func.h | 102 |
7 files changed, 5253 insertions, 0 deletions
diff --git a/erts/emulator/drivers/win32/mem_drv.c b/erts/emulator/drivers/win32/mem_drv.c new file mode 100644 index 0000000000..fa7c46eca8 --- /dev/null +++ b/erts/emulator/drivers/win32/mem_drv.c @@ -0,0 +1,141 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1997-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +/* Purpose: Access to elib memory statistics */ + +#include "sys.h" +#include "erl_driver.h" +#include "elib_stat.h" + +#define MAP_BUF_SIZE 1000 /* Max map size */ +#define HISTO_BUF_SIZE 100 /* Max histogram buckets */ + +static ErlDrvData mem_start(ErlDrvPort, char*); +static int mem_init(void); +static void mem_stop(ErlDrvData); +static void mem_command(ErlDrvData); + +ErlDrvEntry mem_driver_entry = { + mem_init, + mem_start, + mem_stop, + mem_command, + NULL, + NULL, + "mem_drv" +}; + +static int mem_init(void) +{ + return 0; +} + +static ErlDrvData mem_start(ErlDrvPort port, char* buf) +{ + return (ErlDrvData)port; +} + +static void mem_stop(ErlDrvData port) +{ +} + +void putint32(p, v) +byte* p; int v; +{ + p[0] = (v >> 24) & 0xff; + p[1] = (v >> 16) & 0xff; + p[2] = (v >> 8) & 0xff; + p[3] = (v) & 0xff; +} + +int getint16(p) +byte* p; +{ + return (p[0] << 8) | p[1]; +} + +/* +** Command: +** m L1 L0 -> a heap map of length L1*256 + L0 is returned +** s -> X3 X2 X1 X0 Y3 Y2 Y1 Y0 Z3 Z2 Z1 Z0 +** X == Total heap size bytes +** Y == Total free bytes +** Z == Size of largest free block in bytes +** +** h L1 L0 B0 -> Generate a logarithm histogram base B with L buckets +** l L1 L0 S0 -> Generate a linear histogram with step S with L buckets +*/ +unsigned char outbuf[HISTO_BUF_SIZE*2*4]; + +static void mem_command(ErlDrvData port, char* buf, int count) +{ + if ((count == 1) && buf[0] == 's') { + struct elib_stat info; + char v[3*4]; + + elib_stat(&info); + + putint32(v, info.mem_total*4); + putint32(v+4, info.mem_free*4); + putint32(v+8, info.max_free*4); + driver_output((ErlDrvPort)port, v, 12); + return; + } + else if ((count == 3) && buf[0] == 'm') { + char w[MAP_BUF_SIZE]; + int n = getint16(buf+1); + + if (n > MAP_BUF_SIZE) + n = MAP_BUF_SIZE; + elib_heap_map(w, n); + driver_output((ErlDrvPort)port, w, n); + return; + } + else if ((count == 4) && (buf[0] == 'h' || buf[0] == 'l')) { + unsigned long vf[HISTO_BUF_SIZE]; + unsigned long va[HISTO_BUF_SIZE]; + int n = getint16(buf+1); + int base = (unsigned char) buf[3]; + + if (n >= HISTO_BUF_SIZE) + n = HISTO_BUF_SIZE; + if (buf[0] == 'l') + base = -base; + if (elib_histo(vf, va, n, base) < 0) { + driver_failure((ErlDrvPort)port, -1); + return; + } + else { + char* p = outbuf; + int i; + + for (i = 0; i < n; i++) { + putint32(p, vf[i]); + p += 4; + } + for (i = 0; i < n; i++) { + putint32(p, va[i]); + p += 4; + } + driver_output((ErlDrvPort)port, outbuf, n*8); + } + return; + } + driver_failure((ErlDrvPort)port, -1); +} + diff --git a/erts/emulator/drivers/win32/registry_drv.c b/erts/emulator/drivers/win32/registry_drv.c new file mode 100644 index 0000000000..05fd2ea55f --- /dev/null +++ b/erts/emulator/drivers/win32/registry_drv.c @@ -0,0 +1,535 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1997-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +/* + * Purpose: Interface to the registry API. + */ + +#include <windows.h> +#include "erl_driver.h" +#include "sys.h" + +/* + * Commands recognised by this driver. + */ + +#define CMD_GET_CURRENT 0 +#define CMD_OPEN_KEY 1 +#define CMD_CREATE_KEY 2 +#define CMD_GET_ALL_SUBKEYS 3 +#define CMD_GET_VALUE 4 +#define CMD_GET_ALL_VALUES 5 +#define CMD_SET_VALUE 6 +#define CMD_DELETE_KEY 7 +#define CMD_DELETE_VALUE 8 + +/* + * Microsoft-specific function to map a WIN32 error code to a Posix errno. + */ + +extern void _dosmaperr(DWORD); + +/* + * Information kept for a registry port (since there is no controlling + * Erlang process, all state must be kept here). + */ + +typedef struct { + ErlDrvPort port; /* Port handle. */ + REGSAM sam; /* Access for handles. */ + HKEY hkey; /* Handle to open key. */ + HKEY hkey_root; /* Root handle for current key. */ + char* key; /* Name of key. */ + DWORD key_size; /* Size of key. */ + LPSTR name_buf; /* Buffer for names. */ + DWORD name_buf_size; /* Size of name buffer. */ + LPSTR value_buf; /* Buffer for values. */ + DWORD value_buf_size; /* Size of value buffer. */ +} RegPort; + + +/* + * Local functions. + */ + +static void reply(RegPort* rp, LONG result); +static BOOL fix_value_result(RegPort* rp, LONG result, DWORD type, + LPSTR name, DWORD nameSize, LPSTR value, + DWORD valueSize); +static int key_reply(RegPort* rp, LPSTR name, DWORD nameSize); +static int value_reply(RegPort* rp, DWORD type, LPSTR name, DWORD nameSize, + LPSTR value, DWORD valueSize); +static int state_reply(RegPort* rp, HKEY root, LPSTR name, DWORD nameSize); +static int maperror(DWORD error); +/* + * Local variables. + */ + +static int reg_init(void); +static ErlDrvData reg_start(ErlDrvPort, char*); +static void reg_stop(ErlDrvData); +static void reg_from_erlang(ErlDrvData, char*, int); + +struct erl_drv_entry registry_driver_entry = { + reg_init, + reg_start, + reg_stop, + reg_from_erlang, + NULL, + NULL, + "registry__drv__", + NULL, + NULL, + NULL, + NULL, + NULL +}; + +static int +reg_init(void) +{ + DEBUGF(("reg_init()\n")); + return 0; +} + +static ErlDrvData +reg_start(ErlDrvPort port, char* buf) +{ + RegPort* rp; + char* s; + REGSAM sam = KEY_READ; + + if ((s = strchr(buf, ' ')) != NULL) { + while (isspace(*s)) + s++; + while (*s != '\0') { + if (*s == 'r') { + sam |= KEY_READ; + } else if (*s == 'w') { + sam |= KEY_WRITE; + } + s++; + } + } + + rp = driver_alloc(sizeof(RegPort)); + if (rp == NULL) { + return ERL_DRV_ERROR_GENERAL; + } + rp->port = port; + rp->hkey = rp->hkey_root = HKEY_CLASSES_ROOT; + rp->sam = sam; + rp->key = driver_alloc(1); + rp->key_size = 0; + rp->name_buf_size = 64; + rp->name_buf = driver_alloc(rp->name_buf_size); + rp->value_buf_size = 64; + rp->value_buf = driver_alloc(rp->value_buf_size); + return (ErlDrvData) rp; +} + +static void +reg_stop(ErlDrvData clientData) +{ + RegPort* rp = (RegPort *) clientData; + + (void) RegCloseKey(rp->hkey); + driver_free(rp->name_buf); + driver_free(rp->value_buf); + driver_free(rp->key); + driver_free(rp); + /* return 1; */ +} + +static void +reg_from_erlang(ErlDrvData clientData, char* buf, int count) +{ + RegPort* rp = (RegPort *) clientData; + int cmd; + HKEY hkey; + LONG result; + DWORD nameSize; + DWORD type; /* Type of data in buffer. */ + DWORD valueSize; /* Size of value buffer. */ + + cmd = buf[0]; + buf++, count--; + switch (cmd) { + case CMD_GET_CURRENT: + state_reply(rp, rp->hkey_root, rp->key, rp->key_size); + break; + case CMD_OPEN_KEY: + { + char* key; + HKEY newKey; + + /* + * [HKEY(DWORD), KeyString(string)] + */ + + hkey = (HKEY) get_int32(buf+0); + rp->hkey_root = hkey; + key = buf+4; + result = RegOpenKeyEx(hkey, key, 0, rp->sam, &newKey); + if (result == ERROR_SUCCESS) { + RegCloseKey(rp->hkey); + rp->hkey = newKey; + driver_free(rp->key); + rp->key_size = strlen(key); + rp->key = driver_alloc(rp->key_size+1); + strcpy(rp->key, key); + } + reply(rp, result); + return; + } + break; + case CMD_CREATE_KEY: + { + char* key; + HKEY newKey; + DWORD disposition; + + hkey = (HKEY) get_int32(buf+0); + rp->hkey_root = hkey; + key = buf+4; + result = RegCreateKeyEx(hkey, key, 0, "", 0, rp->sam, NULL, + &newKey, &disposition); + if (result == ERROR_SUCCESS) { + RegCloseKey(rp->hkey); + rp->hkey = newKey; + driver_free(rp->key); + rp->key_size = strlen(key); + rp->key = driver_alloc(rp->key_size+1); + strcpy(rp->key, key); + } + reply(rp, result); + return; + } + break; + case CMD_GET_ALL_SUBKEYS: + { + int i; + + i = 0; + for (;;) { + nameSize = rp->name_buf_size; + result = RegEnumKeyEx(rp->hkey, i, rp->name_buf, &nameSize, + NULL, NULL, NULL, NULL); + if (result == ERROR_MORE_DATA) { + rp->name_buf_size *= 2; + rp->name_buf = driver_realloc(rp->name_buf, + rp->name_buf_size); + continue; + } else if (result == ERROR_NO_MORE_ITEMS) { + reply(rp, ERROR_SUCCESS); + return; + } else if (result != ERROR_SUCCESS) { + reply(rp, result); + return; + } + key_reply(rp, rp->name_buf, nameSize); + i++; + } + } + break; + case CMD_GET_VALUE: + do { + valueSize = rp->value_buf_size; + result = RegQueryValueEx(rp->hkey, buf, NULL, &type, + rp->value_buf, &valueSize); + } while (!fix_value_result(rp, result, type, buf, strlen(buf), + rp->value_buf, valueSize)); + break; + case CMD_GET_ALL_VALUES: + { + int i; + + i = 0; + for (;;) { + nameSize = rp->name_buf_size; + valueSize = rp->value_buf_size; + result = RegEnumValue(rp->hkey, i, rp->name_buf, &nameSize, + NULL, &type, rp->value_buf, &valueSize); + if (result == ERROR_NO_MORE_ITEMS) { + reply(rp, ERROR_SUCCESS); + return; + } + if (fix_value_result(rp, result, type, rp->name_buf, nameSize, + rp->value_buf, valueSize)) { + i++; + } + } + } + break; + case CMD_SET_VALUE: + { + LPSTR name; + DWORD dword; + + /* + * [Type(DWORD), Name(string), Value(bytes)] + */ + + type = get_int32(buf); + buf += 4; + count -= 4; + name = buf; + nameSize = strlen(buf) + 1; + buf += nameSize; + count -= nameSize; + if (type == REG_DWORD) { + /* + * Must pass a pointer to a DWORD in host byte order. + */ + dword = get_int32(buf); + buf = (char *) &dword; + ASSERT(count == 4); + } + result = RegSetValueEx(rp->hkey, name, 0, type, buf, count); + reply(rp, result); + } + break; + case CMD_DELETE_KEY: + result = RegDeleteKey(rp->hkey, NULL); + reply(rp, result); + break; + case CMD_DELETE_VALUE: + result = RegDeleteValue(rp->hkey, buf); + reply(rp, result); + break; + } + /* return 1; */ +} + +static BOOL +fix_value_result(RegPort* rp, LONG result, DWORD type, + LPSTR name, DWORD nameSize, LPSTR value, DWORD valueSize) +{ + if (result == ERROR_MORE_DATA) { + DWORD max_name1; + DWORD max_name2; + DWORD max_value; + int ok; + + ok = RegQueryInfoKey(rp->hkey, NULL, NULL, NULL, + NULL, &max_name1, NULL, NULL, &max_name2, + &max_value, NULL, NULL); +#ifdef DEBUG + if (ok != ERROR_SUCCESS) { + char buff[256]; + sprintf(buff,"Failure in registry_drv line %d, error = %d", + __LINE__, GetLastError()); + MessageBox(NULL, buff, "Internal error", MB_OK); + ASSERT(ok == ERROR_SUCCESS); + } +#endif + rp->name_buf_size = (max_name1 > max_name2 ? max_name1 : max_name2) + + 1; + rp->value_buf_size = max_value + 1; + rp->name_buf = driver_realloc(rp->name_buf, rp->name_buf_size); + rp->value_buf = driver_realloc(rp->value_buf, rp->value_buf_size); + return FALSE; + } else if (result != ERROR_SUCCESS) { + reply(rp, result); + return TRUE; + } + + /* + * Do some data conversion which is easier to do here + * than in Erlang. + */ + + switch (type) { + case REG_SZ: + case REG_EXPAND_SZ: + valueSize--; /* No reason to send the '\0' to Erlang. */ + break; + case REG_DWORD_LITTLE_ENDIAN: + case REG_DWORD_BIG_ENDIAN: + /* + * The value is a DWORD stored in host byte order. + * We must retrieve it and store it in network byte order. + */ + { + DWORD dword = * ((DWORD *) value); + put_int32(dword, value); + type = REG_DWORD; /* Simplify life for Erlang code. */ + break; + } + } + + return value_reply(rp, type, name, nameSize, value, valueSize); +} + +/* + * Sends one of the following replies back to Erlang, + * depending on result: + * + * [$e|Posix error(string)] Error + * [$o] Ok + */ + +static void +reply(RegPort* rp, LONG result) +{ + char sbuf[256]; + + if (result == ERROR_SUCCESS) { + sbuf[0] = 'o'; + driver_output(rp->port, sbuf, 1); + } else { + char* s; + char* t; + int err; + + sbuf[0] = 'e'; + err = maperror(result); + for (s = erl_errno_id(err), t = sbuf+1; *s; s++, t++) { + *t = tolower(*s); + } + driver_output(rp->port, sbuf, t-sbuf); + } + /* return 1; */ +} + +/* + * Sends a key to Erlang: + * + * [$k, Keyname(string)] + */ + +static int +key_reply(RegPort* rp, /* Pointer to port structure. */ + LPSTR name, /* Pointer to name. */ + DWORD nameSize) /* Length of name. */ +{ + char sbuf[512]; + char* s = sbuf; + int needed = 1+nameSize; + + if (sizeof sbuf < needed) { + s = driver_alloc(needed); + } + + s[0] = 'k'; + memcpy(s+1, name, nameSize); + driver_output(rp->port, s, needed); + + if (s != sbuf) { + driver_free(s); + } + return 1; +} + +/* + * Sends a value to Erlang: + * + * [$v, Type(DWORD), Valuename(string), 0, Value(bytes)] + */ + +static int +value_reply(RegPort* rp, /* Pointer to port structure. */ + DWORD type, /* Type of value */ + LPSTR name, /* Pointer to name. */ + DWORD nameSize, /* Length of name. */ + LPSTR value, /* Pointer to value. */ + DWORD valueSize) /* Size of value. */ +{ + char sbuf[512]; + char* s = sbuf; + int needed = 1+4+nameSize+1+valueSize; + int i; + + if (sizeof sbuf < needed) { + s = driver_alloc(needed); + } + + s[0] = 'v'; + i = 1; + put_int32(type, s+i); + i += 4; + memcpy(s+i, name, nameSize); + i += nameSize; + s[i++] = '\0'; + memcpy(s+i, value, valueSize); + ASSERT(i+valueSize == needed); + driver_output(rp->port, s, needed); + + if (s != sbuf) { + driver_free(s); + } + return 1; +} + +/* + * Sends a key to Erlang: + * + * [$s, HKEY(DWORD), Keyname(string)] State + */ + +static int +state_reply(RegPort* rp, /* Pointer to port structure. */ + HKEY root, /* Handle to root key for this key. */ + LPSTR name, /* Pointer to name. */ + DWORD nameSize) /* Length of name. */ +{ + char sbuf[512]; + char* s = sbuf; + int needed = 1+4+nameSize; + int i; + + if (sizeof sbuf < needed) { + s = driver_alloc(needed); + } + + s[0] = 's'; + i = 1; + put_int32((DWORD) root, s+i); + i += 4; + memcpy(s+i, name, nameSize); + ASSERT(i+nameSize == needed); + driver_output(rp->port, s, needed); + + if (s != sbuf) { + driver_free(s); + } + return 1; +} + +static int +maperror(DWORD error) +{ + DEBUGF(("Mapping %d\n", error)); + switch (error) { + case ERROR_BADDB: + case ERROR_BADKEY: + case ERROR_CANTOPEN: + case ERROR_CANTWRITE: + case ERROR_REGISTRY_RECOVERED: + case ERROR_REGISTRY_CORRUPT: + case ERROR_REGISTRY_IO_FAILED: + case ERROR_NOT_REGISTRY_FILE: + return EIO; + case ERROR_KEY_DELETED: + return EINVAL; + default: + _dosmaperr(error); + return errno; + } +} diff --git a/erts/emulator/drivers/win32/ttsl_drv.c b/erts/emulator/drivers/win32/ttsl_drv.c new file mode 100644 index 0000000000..fd88dafd34 --- /dev/null +++ b/erts/emulator/drivers/win32/ttsl_drv.c @@ -0,0 +1,751 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1996-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +/* + * Tty driver that reads one character at the time and provides a + * smart line for output. + */ + +#include "sys.h" +#include <ctype.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <signal.h> + +#include "erl_driver.h" +#include "win_con.h" + +#define TRUE 1 +#define FALSE 0 + +static int cols; /* Number of columns available. */ +static int rows; /* Number of rows available. */ + +/* The various opcodes. */ +#define OP_PUTC 0 +#define OP_MOVE 1 +#define OP_INSC 2 +#define OP_DELC 3 +#define OP_BEEP 4 + +/* Control op */ +#define CTRL_OP_GET_WINSIZE 100 +#define CTRL_OP_GET_UNICODE_STATE 101 +#define CTRL_OP_SET_UNICODE_STATE 102 + +static int lbuf_size = BUFSIZ; +Uint32 *lbuf; /* The current line buffer */ +int llen; /* The current line length */ +int lpos; /* The current "cursor position" in the line buffer */ + +/* + * Tags used in line buffer to show that these bytes represent special characters, + * Max unicode is 0x0010ffff, so we have lots of place for meta tags... + */ +#define CONTROL_TAG 0x10000000U /* Control character, value in first position */ +#define ESCAPED_TAG 0x01000000U /* Escaped character, value in first position */ +#define TAG_MASK 0xFF000000U + +#define MAXSIZE (1 << 16) + +#define ISPRINT(c) (isprint(c) || (128+32 <= (c) && (c) < 256)) + +#define DEBUGLOG(X) /* nothing */ + +/* + * XXX These are used by win_con.c (for command history). + * Should be cleaned up. + */ + + +#define NL '\n' + +/* Main interface functions. */ +static int ttysl_init(void); +static ErlDrvData ttysl_start(ErlDrvPort, char*); +static void ttysl_stop(ErlDrvData); +static int ttysl_control(ErlDrvData, unsigned int, char *, int, char **, int); +static void ttysl_from_erlang(ErlDrvData, char*, int); +static void ttysl_from_tty(ErlDrvData, ErlDrvEvent); +static Sint16 get_sint16(char *s); + +static ErlDrvPort ttysl_port; + +extern ErlDrvEvent console_input_event; +extern HANDLE console_thread; + +static HANDLE ttysl_in = INVALID_HANDLE_VALUE; /* Handle for console input. */ +static HANDLE ttysl_out = INVALID_HANDLE_VALUE; /* Handle for console output */ + +/* Functions that work on the line buffer. */ +static int start_lbuf(); +static int stop_lbuf(); +static int put_chars(); +static int move_rel(); +static int ins_chars(); +static int del_chars(); +static int step_over_chars(int n); +static int insert_buf(); +static int write_buf(); +static void move_cursor(int, int); + +/* Define the driver table entry. */ +struct erl_drv_entry ttsl_driver_entry = { + ttysl_init, + ttysl_start, + ttysl_stop, + ttysl_from_erlang, + ttysl_from_tty, + NULL, + "tty_sl", + NULL, + NULL, + ttysl_control, + NULL +}; + +static int utf8_mode = 0; + +static int ttysl_init() +{ + lbuf = NULL; /* For line buffer handling */ + ttysl_port = (ErlDrvPort)-1; + return 0; +} + +static ErlDrvData ttysl_start(ErlDrvPort port, char* buf) +{ + if ((int)ttysl_port != -1 || console_thread == NULL) { + return ERL_DRV_ERROR_GENERAL; + } + start_lbuf(); + utf8_mode = 1; + driver_select(port, console_input_event, ERL_DRV_READ, 1); + ttysl_port = port; + return (ErlDrvData)ttysl_port;/* Nothing important to return */ +} + +#define DEF_HEIGHT 24 +#define DEF_WIDTH 80 + +static void ttysl_get_window_size(Uint32 *width, Uint32 *height) +{ + *width = ConGetColumns(); + *height = ConGetRows(); +} + + +static int ttysl_control(ErlDrvData drv_data, + unsigned int command, + char *buf, int len, + char **rbuf, int rlen) +{ + char resbuff[2*sizeof(Uint32)]; + int res_size; + switch (command) { + case CTRL_OP_GET_WINSIZE: + { + Uint32 w,h; + ttysl_get_window_size(&w,&h); + memcpy(resbuff,&w,sizeof(Uint32)); + memcpy(resbuff+sizeof(Uint32),&h,sizeof(Uint32)); + res_size = 2*sizeof(Uint32); + } + break; + case CTRL_OP_GET_UNICODE_STATE: + *resbuff = (utf8_mode) ? 1 : 0; + res_size = 1; + break; + case CTRL_OP_SET_UNICODE_STATE: + if (len > 0) { + int m = (int) *buf; + *resbuff = (utf8_mode) ? 1 : 0; + res_size = 1; + utf8_mode = (m) ? 1 : 0; + } else { + return 0; + } + break; + default: + return 0; + } + if (rlen < res_size) { + *rbuf = driver_alloc(res_size); + } + memcpy(*rbuf,resbuff,res_size); + return res_size; +} + + +static void ttysl_stop(ErlDrvData ttysl_data) +{ + if ((int)ttysl_port != -1) { + driver_select(ttysl_port, console_input_event, ERL_DRV_READ, 0); + } + + ttysl_in = ttysl_out = INVALID_HANDLE_VALUE; + stop_lbuf(); + ttysl_port = (ErlDrvPort)-1; +} + +static int put_utf8(int ch, byte *target, int sz, int *pos) +{ + Uint x = (Uint) ch; + if (x < 0x80) { + if (*pos >= sz) { + return -1; + } + target[(*pos)++] = (byte) x; + } + else if (x < 0x800) { + if (((*pos) + 1) >= sz) { + return -1; + } + target[(*pos)++] = (((byte) (x >> 6)) | + ((byte) 0xC0)); + target[(*pos)++] = (((byte) (x & 0x3F)) | + ((byte) 0x80)); + } else if (x < 0x10000) { + if ((x >= 0xD800 && x <= 0xDFFF) || + (x == 0xFFFE) || + (x == 0xFFFF)) { /* Invalid unicode range */ + return -1; + } + if (((*pos) + 2) >= sz) { + return -1; + } + + target[(*pos)++] = (((byte) (x >> 12)) | + ((byte) 0xE0)); + target[(*pos)++] = ((((byte) (x >> 6)) & 0x3F) | + ((byte) 0x80)); + target[(*pos)++] = (((byte) (x & 0x3F)) | + ((byte) 0x80)); + } else if (x < 0x110000) { /* Standard imposed max */ + if (((*pos) + 3) >= sz) { + return -1; + } + target[(*pos)++] = (((byte) (x >> 18)) | + ((byte) 0xF0)); + target[(*pos)++] = ((((byte) (x >> 12)) & 0x3F) | + ((byte) 0x80)); + target[(*pos)++] = ((((byte) (x >> 6)) & 0x3F) | + ((byte) 0x80)); + target[(*pos)++] = (((byte) (x & 0x3F)) | + ((byte) 0x80)); + } else { + return -1; + } + return 0; +} + + +static int pick_utf8(byte *s, int sz, int *pos) +{ + int size = sz - (*pos); + byte *source; + Uint unipoint; + + if (size > 0) { + source = s + (*pos); + if (((*source) & ((byte) 0x80)) == 0) { + unipoint = (int) *source; + ++(*pos); + return (int) unipoint; + } else if (((*source) & ((byte) 0xE0)) == 0xC0) { + if (size < 2) { + return -2; + } + if (((source[1] & ((byte) 0xC0)) != 0x80) || + ((*source) < 0xC2) /* overlong */) { + return -1; + } + (*pos) += 2; + unipoint = + (((Uint) ((*source) & ((byte) 0x1F))) << 6) | + ((Uint) (source[1] & ((byte) 0x3F))); + return (int) unipoint; + } else if (((*source) & ((byte) 0xF0)) == 0xE0) { + if (size < 3) { + return -2; + } + if (((source[1] & ((byte) 0xC0)) != 0x80) || + ((source[2] & ((byte) 0xC0)) != 0x80) || + (((*source) == 0xE0) && (source[1] < 0xA0)) /* overlong */ ) { + return -1; + } + if ((((*source) & ((byte) 0xF)) == 0xD) && + ((source[1] & 0x20) != 0)) { + return -1; + } + if (((*source) == 0xEF) && (source[1] == 0xBF) && + ((source[2] == 0xBE) || (source[2] == 0xBF))) { + return -1; + } + (*pos) += 3; + unipoint = + (((Uint) ((*source) & ((byte) 0xF))) << 12) | + (((Uint) (source[1] & ((byte) 0x3F))) << 6) | + ((Uint) (source[2] & ((byte) 0x3F))); + return (int) unipoint; + } else if (((*source) & ((byte) 0xF8)) == 0xF0) { + if (size < 4) { + return -2 ; + } + if (((source[1] & ((byte) 0xC0)) != 0x80) || + ((source[2] & ((byte) 0xC0)) != 0x80) || + ((source[3] & ((byte) 0xC0)) != 0x80) || + (((*source) == 0xF0) && (source[1] < 0x90)) /* overlong */) { + return -1; + } + if ((((*source) & ((byte)0x7)) > 0x4U) || + ((((*source) & ((byte)0x7)) == 0x4U) && + ((source[1] & ((byte)0x3F)) > 0xFU))) { + return -1; + } + (*pos) += 4; + unipoint = + (((Uint) ((*source) & ((byte) 0x7))) << 18) | + (((Uint) (source[1] & ((byte) 0x3F))) << 12) | + (((Uint) (source[2] & ((byte) 0x3F))) << 6) | + ((Uint) (source[3] & ((byte) 0x3F))); + return (int) unipoint; + } else { + return -1; + } + } else { + return -1; + } +} + +static int octal_or_hex_positions(Uint c) +{ + int x = 0; + Uint ch = c; + if (!ch) { + return 1; + } + while(ch) { + ++x; + ch >>= 3; + } + if (x <= 3) { + return 3; + } + /* \x{H ...} format when larger than \777 */ + x = 0; + ch = c; + while(ch) { + ++x; + ch >>= 4; + } + return x+3; +} + +static void octal_or_hex_format(Uint ch, byte *buf, int *pos) +{ + static byte hex_chars[] = { '0','1','2','3','4','5','6','7','8','9', + 'A','B','C','D','E','F'}; + int num = octal_or_hex_positions(ch); + if (num != 3) { + buf[(*pos)++] = 'x'; + buf[(*pos)++] = '{'; + num -= 3; + while(num--) { + buf[(*pos)++] = hex_chars[((ch >> (4*num)) & 0xFU)]; + } + buf[(*pos)++] = '}'; + } else { + while(num--) { + buf[(*pos)++] = ((byte) ((ch >> (3*num)) & 0x7U) + '0'); + } + } +} + +/* + * Check that there is enough room in all buffers to copy all pad chars + * and stiff we need If not, realloc lbuf. + */ +static int check_buf_size(byte *s, int n) +{ + int pos = 0; + int ch; + int size = 10; + + while(pos < n) { + /* Indata is always UTF-8 */ + if ((ch = pick_utf8(s,n,&pos)) < 0) { + /* XXX temporary allow invalid chars */ + ch = (int) s[pos]; + DEBUGLOG(("Invalid UTF8:%d",ch)); + ++pos; + } + if (utf8_mode) { /* That is, terminal is UTF8 compliant */ + if (ch >= 128 || isprint(ch)) { + DEBUGLOG(("Printable(UTF-8:%d):%d",(pos - opos),ch)); + size++; /* Buffer contains wide characters... */ + } else if (ch == '\t') { + size += 8; + } else { + DEBUGLOG(("Magic(UTF-8:%d):%d",(pos - opos),ch)); + size += 2; + } + } else { + if (ch <= 255 && isprint(ch)) { + DEBUGLOG(("Printable:%d",ch)); + size++; + } else if (ch == '\t') + size += 8; + else if (ch >= 128) { + DEBUGLOG(("Non printable:%d",ch)); + size += (octal_or_hex_positions(ch) + 1); + } + else { + DEBUGLOG(("Magic:%d",ch)); + size += 2; + } + } + } + + if (size + lpos >= lbuf_size) { + + lbuf_size = size + lpos + BUFSIZ; + if ((lbuf = driver_realloc(lbuf, lbuf_size * sizeof(Uint32))) == NULL) { + driver_failure(ttysl_port, -1); + return(0); + } + } + return(1); +} + + +static void ttysl_from_erlang(ErlDrvData ttysl_data, char* buf, int count) +{ + if (lpos > MAXSIZE) + put_chars((byte*)"\n", 1); + + switch (buf[0]) { + case OP_PUTC: + DEBUGLOG(("OP: Putc(%d)",count-1)); + if (check_buf_size((byte*)buf+1, count-1) == 0) + return; + put_chars((byte*)buf+1, count-1); + break; + case OP_MOVE: + move_rel(get_sint16(buf+1)); + break; + case OP_INSC: + if (check_buf_size((byte*)buf+1, count-1) == 0) + return; + ins_chars((byte*)buf+1, count-1); + break; + case OP_DELC: + del_chars(get_sint16(buf+1)); + break; + case OP_BEEP: + ConBeep(); + break; + default: + /* Unknown op, just ignore. */ + break; + } + return; +} + +extern int read_inbuf(char *data, int n); + +static void ttysl_from_tty(ErlDrvData ttysl_data, ErlDrvEvent fd) +{ + Uint32 inbuf[64]; + byte t[1024]; + int i,pos,tpos; + + i = ConReadInput(inbuf,1); + + pos = 0; + tpos = 0; + + while (pos < i) { + while (tpos < 1020 && pos < i) { /* Max 4 bytes for UTF8 */ + put_utf8((int) inbuf[pos++], t, 1024, &tpos); + } + driver_output(ttysl_port, (char *) t, tpos); + tpos = 0; + } +} + +/* + * Gets signed 16 bit integer from binary buffer. + */ +static Sint16 +get_sint16(char *s) +{ + return ((*s << 8) | ((byte*)s)[1]); +} + + +static int start_lbuf(void) +{ + if (!lbuf && !(lbuf = ( Uint32*) driver_alloc(lbuf_size * sizeof(Uint32)))) + return FALSE; + llen = 0; + lpos = 0; + return TRUE; +} + +static int stop_lbuf(void) +{ + if (lbuf) { + driver_free(lbuf); + lbuf = NULL; + } + llen = 0; /* To avoid access error in win_con:AddToCmdHistory during exit*/ + return TRUE; +} + +/* Put l bytes (in UTF8) from s into the buffer and output them. */ +static int put_chars(byte *s, int l) +{ + int n; + + n = insert_buf(s, l); + if (n > 0) + write_buf(lbuf + lpos - n, n); + if (lpos > llen) + llen = lpos; + return TRUE; +} + +/* + * Move the current postition forwards or backwards within the current + * line. We know about padding. + */ +static int move_rel(int n) +{ + int npos; /* The new position */ + + /* Step forwards or backwards over the buffer. */ + npos = step_over_chars(n); + + /* Calculate move, updates pointers and move the cursor. */ + move_cursor(lpos, npos); + lpos = npos; + return TRUE; +} + +/* Insert characters into the buffer at the current position. */ +static int ins_chars(byte *s, int l) +{ + int n, tl; + Uint32 *tbuf = NULL; /* Suppress warning about use-before-set */ + + /* Move tail of buffer to make space. */ + if ((tl = llen - lpos) > 0) { + if ((tbuf = driver_alloc(tl * sizeof(Uint32))) == NULL) + return FALSE; + memcpy(tbuf, lbuf + lpos, tl * sizeof(Uint32)); + } + n = insert_buf(s, l); + if (tl > 0) { + memcpy(lbuf + lpos, tbuf, tl * sizeof(Uint32)); + driver_free(tbuf); + } + llen += n; + write_buf(lbuf + (lpos - n), llen - (lpos - n)); + move_cursor(llen, lpos); + return TRUE; +} + +/* + * Delete characters in the buffer. Can delete characters before (n < 0) + * and after (n > 0) the current position. Cursor left at beginning of + * deleted block. + */ +static int del_chars(int n) +{ + int i, l, r; + int pos; + + /*update_cols();*/ + + /* Step forward or backwards over n logical characters. */ + pos = step_over_chars(n); + + if (pos > lpos) { + l = pos - lpos; /* Buffer characters to delete */ + r = llen - lpos - l; /* Characters after deleted */ + /* Fix up buffer and buffer pointers. */ + if (r > 0) + memcpy(lbuf + lpos, lbuf + pos, r * sizeof(Uint32)); + llen -= l; + /* Write out characters after, blank the tail and jump back to lpos. */ + write_buf(lbuf + lpos, r); + for (i = l ; i > 0; --i) + ConPutChar(' '); + move_cursor(llen + l, lpos); + } + else if (pos < lpos) { + l = lpos - pos; /* Buffer characters */ + r = llen - lpos; /* Characters after deleted */ + move_cursor(lpos, lpos-l); /* Move back */ + /* Fix up buffer and buffer pointers. */ + if (r > 0) + memcpy(lbuf + pos, lbuf + lpos, r * sizeof(Uint32)); + lpos -= l; + llen -= l; + /* Write out characters after, blank the tail and jump back to lpos. */ + write_buf(lbuf + lpos, r); + for (i = l ; i > 0; --i) + ConPutChar(' '); + move_cursor(llen + l, lpos); + } + return TRUE; +} + + +/* Step over n logical characters, check for overflow. */ +static int step_over_chars(int n) +{ + Uint32 *c, *beg, *end; + + beg = lbuf; + end = lbuf + llen; + c = lbuf + lpos; + for ( ; n > 0 && c < end; --n) { + c++; + while (c < end && (*c & TAG_MASK) && ((*c & ~TAG_MASK) == 0)) + c++; + } + for ( ; n < 0 && c > beg; n++) { + --c; + while (c > beg && (*c & TAG_MASK) && ((*c & ~TAG_MASK) == 0)) + --c; + } + return c - lbuf; +} + +static int insert_buf(byte *s, int n) +{ + int pos = 0; + int buffpos = lpos; + int ch; + + while (pos < n) { + if ((ch = pick_utf8(s,n,&pos)) < 0) { + /* XXX temporary allow invalid chars */ + ch = (int) s[pos]; + DEBUGLOG(("insert_buf: Invalid UTF8:%d",ch)); + ++pos; + } + if ((utf8_mode && (ch >= 128 || isprint(ch))) || (ch <= 255 && isprint(ch))) { + DEBUGLOG(("insert_buf: Printable(UTF-8):%d",ch)); + lbuf[lpos++] = (Uint32) ch; + } else if (ch >= 128) { /* not utf8 mode */ + int nc = octal_or_hex_positions(ch); + lbuf[lpos++] = ((Uint32) ch) | ESCAPED_TAG; + while (nc--) { + lbuf[lpos++] = ESCAPED_TAG; + } + } else if (ch == '\t') { + do { + lbuf[lpos++] = (CONTROL_TAG | ((Uint32) ch)); + ch = 0; + } while (lpos % 8); + } else if (ch == '\n' || ch == '\r') { + write_buf(lbuf + buffpos, lpos - buffpos); + ConPutChar('\r'); + if (ch == '\n') + ConPutChar('\n'); + if (llen > lpos) { + memcpy(lbuf, lbuf + lpos, llen - lpos); + } + llen -= lpos; + lpos = buffpos = 0; + } else { + DEBUGLOG(("insert_buf: Magic(UTF-8):%d",ch)); + lbuf[lpos++] = ch | CONTROL_TAG; + lbuf[lpos++] = CONTROL_TAG; + } + } + return lpos - buffpos; /* characters "written" into + current buffer (may be less due to newline) */ +} +static int write_buf(Uint32 *s, int n) +{ + int i; + + /*update_cols();*/ + + while (n > 0) { + if (!(*s & TAG_MASK) ) { + ConPutChar(*s); + --n; + ++s; + } + else if (*s == (CONTROL_TAG | ((Uint32) '\t'))) { + ConPutChar(' '); + --n; s++; + while (n > 0 && *s == CONTROL_TAG) { + ConPutChar(' '); + --n; s++; + } + } else if (*s & CONTROL_TAG) { + ConPutChar('^'); + ConPutChar((*s == 0177) ? '?' : *s | 0x40); + n -= 2; + s += 2; + } else if (*s & ESCAPED_TAG) { + Uint32 ch = *s & ~(TAG_MASK); + byte *octbuff; + byte octtmp[256]; + int octbytes; + DEBUGLOG(("Escaped: %d", ch)); + octbytes = octal_or_hex_positions(ch); + if (octbytes > 256) { + octbuff = driver_alloc(octbytes); + } else { + octbuff = octtmp; + } + octbytes = 0; + octal_or_hex_format(ch, octbuff, &octbytes); + DEBUGLOG(("octbytes: %d", octbytes)); + ConPutChar('\\'); + for (i = 0; i < octbytes; ++i) { + ConPutChar(octbuff[i]); + } + n -= octbytes+1; + s += octbytes+1; + if (octbuff != octtmp) { + driver_free(octbuff); + } + } else { + DEBUGLOG(("Very unexpected character %d",(int) *s)); + ++n; + --s; + } + } + return TRUE; +} + + +static void +move_cursor(int from, int to) +{ + ConSetCursor(from,to); +} diff --git a/erts/emulator/drivers/win32/win_con.c b/erts/emulator/drivers/win32/win_con.c new file mode 100644 index 0000000000..2202ca655f --- /dev/null +++ b/erts/emulator/drivers/win32/win_con.c @@ -0,0 +1,2259 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1997-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +#define UNICODE 1 +#define _UNICODE 1 +#include <tchar.h> +#include <stdio.h> +#include "sys.h" +#include <windowsx.h> +#include "resource.h" +#include "erl_version.h" +#include <commdlg.h> +#include <commctrl.h> +#include "erl_driver.h" +#include "win_con.h" + +#define ALLOC(X) malloc(X) +#define REALLOC(X,Y) realloc(X,Y) +#define FREE(X) free(X) + +#ifndef STATE_SYSTEM_INVISIBLE +/* Mingw problem with oleacc.h and WIN32_LEAN_AND_MEAN */ +#define STATE_SYSTEM_INVISIBLE 0x00008000 +#endif + +#define WM_CONTEXT (0x0401) +#define WM_CONBEEP (0x0402) +#define WM_SAVE_PREFS (0x0403) + +#define USER_KEY TEXT("Software\\Ericsson\\Erlang\\") TEXT(ERLANG_VERSION) + +#define FRAME_HEIGHT ((2*GetSystemMetrics(SM_CYEDGE))+(2*GetSystemMetrics(SM_CYFRAME))+GetSystemMetrics(SM_CYCAPTION)) +#define FRAME_WIDTH (2*GetSystemMetrics(SM_CXFRAME)+(2*GetSystemMetrics(SM_CXFRAME))+GetSystemMetrics(SM_CXVSCROLL)) + +#define LINE_LENGTH canvasColumns +#define COL(_l) ((_l) % LINE_LENGTH) +#define LINE(_l) ((_l) / LINE_LENGTH) + +#ifdef UNICODE +/* + * We use a character in the invalid unicode range + */ +#define SET_CURSOR (0xD8FF) +#else +/* + * XXX There is no escape to send a character 0x80. Fortunately, + * the ttsl driver currently replaces 0x80 with an octal sequence. + */ +#define SET_CURSOR (0x80) +#endif + +#define SCAN_CODE_BREAK 0x46 /* scan code for Ctrl-Break */ + + +typedef struct ScreenLine_s { + struct ScreenLine_s* next; + struct ScreenLine_s* prev; + int width; +#ifdef HARDDEBUG + int allocated; +#endif + int newline; /* Ends with hard newline: 1, wrapped at end: 0 */ + TCHAR *text; +} ScreenLine_t; + +extern Uint32 *lbuf; /* The current line buffer */ +extern int llen; /* The current line length */ +extern int lpos; + +HANDLE console_input_event; +HANDLE console_thread = NULL; + +#define DEF_CANVAS_COLUMNS 80 +#define DEF_CANVAS_ROWS 26 + +#define BUFSIZE 4096 +#define MAXBUFSIZE 32768 +typedef struct { + TCHAR *data; + int size; + int wrPos; + int rdPos; +} buffer_t; + +static buffer_t inbuf; +static buffer_t outbuf; + +static CHOOSEFONT cf; + +static TCHAR szFrameClass[] = TEXT("FrameClass"); +static TCHAR szClientClass[] = TEXT("ClientClass"); +static HWND hFrameWnd; +static HWND hClientWnd; +static HWND hTBWnd; +static HWND hComboWnd; +static HANDLE console_input; +static HANDLE console_output; +static int cxChar,cyChar, cxCharMax; +static int cxClient,cyClient; +static int cyToolBar; +static int iVscrollPos,iHscrollPos; +static int iVscrollMax,iHscrollMax; +static int nBufLines; +static int cur_x; +static int cur_y; +static int canvasColumns = DEF_CANVAS_COLUMNS; +static int canvasRows = DEF_CANVAS_ROWS; +static ScreenLine_t *buffer_top,*buffer_bottom; +static ScreenLine_t* cur_line; +static POINT editBeg,editEnd; +static BOOL fSelecting = FALSE; +static BOOL fTextSelected = FALSE; +static HKEY key; +static BOOL has_key = FALSE; +static LOGFONT logfont; +static DWORD fgColor; +static DWORD bkgColor; +static FILE *logfile = NULL; +static RECT winPos; +static BOOL toolbarVisible; +static BOOL destroyed = FALSE; + +static int lines_to_save = 1000; /* Maximum number of screen lines to save. */ + +#define TITLE_BUF_SZ 256 + +struct title_buf { + TCHAR *name; + TCHAR buf[TITLE_BUF_SZ]; +}; + +static TCHAR *erlang_window_title = TEXT("Erlang"); + +static unsigned __stdcall ConThreadInit(LPVOID param); +static LRESULT CALLBACK ClientWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam); +static LRESULT CALLBACK FrameWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam); +static BOOL CALLBACK AboutDlgProc(HWND hDlg, UINT iMsg, WPARAM wParam, LPARAM lParam); +static ScreenLine_t *ConNewLine(void); +static void DeleteTopLine(void); +static void ensure_line_below(void); +static ScreenLine_t *GetLineFromY(int y); +static void LoadUserPreferences(void); +static void SaveUserPreferences(void); +static void set_scroll_info(HWND hwnd); +static void ConCarriageFeed(int); +static void ConScrollScreen(void); +static BOOL ConChooseFont(HWND hwnd); +static void ConFontInitialize(HWND hwnd); +static void ConSetFont(HWND hwnd); +static void ConChooseColor(HWND hwnd); +static void DrawSelection(HWND hwnd, POINT pt1, POINT pt2); +static void InvertSelectionArea(HWND hwnd); +static void OnEditCopy(HWND hwnd); +static void OnEditPaste(HWND hwnd); +static void OnEditSelAll(HWND hwnd); +static void GetFileName(HWND hwnd, TCHAR *pFile); +static void OpenLogFile(HWND hwnd); +static void CloseLogFile(HWND hwnd); +static void LogFileWrite(TCHAR *buf, int n); +static int write_inbuf(TCHAR *data, int n); +static void init_buffers(void); +static void AddToCmdHistory(void); +static int write_outbuf(TCHAR *data, int num_chars); +static void ConDrawText(HWND hwnd); +static BOOL (WINAPI *ctrl_handler)(DWORD); +static HWND InitToolBar(HWND hwndParent); +static void window_title(struct title_buf *); +static void free_window_title(struct title_buf *); +static void Client_OnMouseMove(HWND hwnd, int x, int y, UINT keyFlags); + +#define CON_VPRINTF_BUF_INC_SIZE 1024 + +static erts_dsprintf_buf_t * +grow_con_vprintf_buf(erts_dsprintf_buf_t *dsbufp, size_t need) +{ + char *buf; + size_t size; + + ASSERT(dsbufp); + + if (!dsbufp->str) { + size = (((need + CON_VPRINTF_BUF_INC_SIZE - 1) + / CON_VPRINTF_BUF_INC_SIZE) + * CON_VPRINTF_BUF_INC_SIZE); + buf = (char *) ALLOC(size * sizeof(char)); + } + else { + size_t free_size = dsbufp->size - dsbufp->str_len; + + if (need <= free_size) + return dsbufp; + + size = need - free_size + CON_VPRINTF_BUF_INC_SIZE; + size = (((size + CON_VPRINTF_BUF_INC_SIZE - 1) + / CON_VPRINTF_BUF_INC_SIZE) + * CON_VPRINTF_BUF_INC_SIZE); + size += dsbufp->size; + buf = (char *) REALLOC((void *) dsbufp->str, + size * sizeof(char)); + } + if (!buf) + return NULL; + if (buf != dsbufp->str) + dsbufp->str = buf; + dsbufp->size = size; + return dsbufp; +} + +static int con_vprintf(char *format, va_list arg_list) +{ + int res,i; + erts_dsprintf_buf_t dsbuf = ERTS_DSPRINTF_BUF_INITER(grow_con_vprintf_buf); + res = erts_vdsprintf(&dsbuf, format, arg_list); + if (res >= 0) { + TCHAR *tmp = ALLOC(dsbuf.str_len*sizeof(TCHAR)); + for (i=0;i<dsbuf.str_len;++i) { + tmp[i] = dsbuf.str[i]; + } + write_outbuf(tmp, dsbuf.str_len); + FREE(tmp); + } + if (dsbuf.str) + FREE((void *) dsbuf.str); + return res; +} + +void +ConInit(void) +{ + unsigned tid; + + console_input = CreateSemaphore(NULL, 0, 1, NULL); + console_output = CreateSemaphore(NULL, 0, 1, NULL); + console_input_event = CreateManualEvent(FALSE); + console_thread = (HANDLE *) _beginthreadex(NULL, 0, + ConThreadInit, + 0, 0, &tid); + + /* Make all erts_*printf on stdout and stderr use con_vprintf */ + erts_printf_stdout_func = con_vprintf; + erts_printf_stderr_func = con_vprintf; +} + +/* + ConNormalExit() is called from erl_exit() when the emulator + is stopping. If the exit has not been initiated by this + console thread (WM_DESTROY or ID_BREAK), the function must + invoke the console thread to save the user preferences. +*/ +void +ConNormalExit(void) +{ + if (!destroyed) + SendMessage(hFrameWnd, WM_SAVE_PREFS, 0L, 0L); +} + +void +ConWaitForExit(void) +{ + ConPrintf("\n\nAbnormal termination\n"); + WaitForSingleObject(console_thread, INFINITE); +} + +void ConSetCtrlHandler(BOOL (WINAPI *handler)(DWORD)) +{ + ctrl_handler = handler; +} + +int ConPutChar(Uint32 c) +{ + TCHAR sbuf[1]; +#ifdef HARDDEBUG + fprintf(stderr,"ConPutChar: %d\n",(int) c); + fflush(stderr); +#endif + sbuf[0] = c; + write_outbuf(sbuf, 1); + return 1; +} + +static int GetXFromLine(HDC hdc, int hscroll, int xpos,ScreenLine_t *pLine) +{ + SIZE size; + int hscrollPix = hscroll * cxChar; + + if (pLine == NULL) { + return 0; + } + + if (pLine->width < xpos) { + return (canvasColumns-hscroll)*cxChar; + } + /* Not needed (?): SelectObject(hdc,CreateFontIndirect(&logfont)); */ + if (GetTextExtentPoint32(hdc,pLine->text,xpos,&size)) { +#ifdef HARDDEBUG + fprintf(stderr,"size.cx:%d\n",(int)size.cx); + fflush(stderr); +#endif + if (hscrollPix >= size.cx) { + return 0; + } + return ((int) size.cx) - hscrollPix; + } else { + return (xpos-hscroll)*cxChar; + } +} + +static int GetXFromCurrentY(HDC hdc, int hscroll, int xpos) { + return GetXFromLine(hdc, hscroll, xpos, GetLineFromY(cur_y)); +} + +void ConSetCursor(int from, int to) +{ TCHAR cmd[9]; + int *p; + //DebugBreak(); + cmd[0] = SET_CURSOR; + /* + * XXX Expect trouble on CPUs which don't allow misaligned read and writes. + */ + p = (int *)&cmd[1]; + *p++ = from; + *p = to; + write_outbuf(cmd, 1 + (2*sizeof(int)/sizeof(TCHAR))); +} + +void ConPrintf(char *format, ...) +{ + va_list va; + + va_start(va, format); + (void) con_vprintf(format, va); + va_end(va); +} + +void ConBeep(void) +{ + SendMessage(hClientWnd, WM_CONBEEP, 0L, 0L); +} + +int ConReadInput(Uint32 *data, int num_chars) +{ + TCHAR *buf; + int nread; + WaitForSingleObject(console_input,INFINITE); + nread = num_chars = min(num_chars,inbuf.wrPos-inbuf.rdPos); + buf = &inbuf.data[inbuf.rdPos]; + inbuf.rdPos += nread; + while (nread--) + *data++ = *buf++; + if (inbuf.rdPos >= inbuf.wrPos) { + inbuf.rdPos = 0; + inbuf.wrPos = 0; + ResetEvent(console_input_event); + } + ReleaseSemaphore(console_input,1,NULL); + return num_chars; +} + +int ConGetKey(void) +{ + Uint32 c; + WaitForSingleObject(console_input,INFINITE); + ResetEvent(console_input_event); + inbuf.rdPos = inbuf.wrPos = 0; + ReleaseSemaphore(console_input,1,NULL); + WaitForSingleObject(console_input_event,INFINITE); + ConReadInput(&c, 1); + return (int) c; +} + +int ConGetColumns(void) +{ + return (int) canvasColumns; /* 32bit atomic on windows */ +} + +int ConGetRows(void) { + return (int) canvasRows; +} + + +static HINSTANCE hInstance; +extern HMODULE beam_module; + +static unsigned __stdcall +ConThreadInit(LPVOID param) +{ + MSG msg; + WNDCLASSEX wndclass; + int iCmdShow; + STARTUPINFO StartupInfo; + HACCEL hAccel; + int x, y, w, h; + struct title_buf title; + + /*DebugBreak();*/ + hInstance = GetModuleHandle(NULL); + StartupInfo.dwFlags = 0; + GetStartupInfo(&StartupInfo); + iCmdShow = StartupInfo.dwFlags & STARTF_USESHOWWINDOW ? + StartupInfo.wShowWindow : SW_SHOWDEFAULT; + + LoadUserPreferences(); + + /* frame window class */ + wndclass.cbSize = sizeof (wndclass); + wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_BYTEALIGNCLIENT; + wndclass.lpfnWndProc = FrameWndProc; + wndclass.cbClsExtra = 0; + wndclass.cbWndExtra = 0; + wndclass.hInstance = hInstance; + wndclass.hIcon = LoadIcon (hInstance, MAKEINTRESOURCE(1)); + wndclass.hCursor = LoadCursor (NULL, IDC_ARROW); + wndclass.hbrBackground = NULL; + wndclass.lpszMenuName = NULL; + wndclass.lpszClassName = szFrameClass; + wndclass.hIconSm = LoadIcon (hInstance, MAKEINTRESOURCE(1)); + RegisterClassExW (&wndclass); + + /* client window class */ + wndclass.cbSize = sizeof (wndclass); + wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + wndclass.lpfnWndProc = ClientWndProc; + wndclass.cbClsExtra = 0; + wndclass.cbWndExtra = 0; + wndclass.hInstance = hInstance; + wndclass.hIcon = LoadIcon (hInstance, MAKEINTRESOURCE(1)); + wndclass.hCursor = LoadCursor (NULL, IDC_ARROW); + wndclass.hbrBackground = CreateSolidBrush(bkgColor); + wndclass.lpszMenuName = NULL; + wndclass.lpszClassName = szClientClass; + wndclass.hIconSm = LoadIcon (hInstance, MAKEINTRESOURCE(1)); + RegisterClassExW (&wndclass); + + InitCommonControls(); + init_buffers(); + + nBufLines = 0; + buffer_top = cur_line = ConNewLine(); + cur_line->next = buffer_bottom = ConNewLine(); + buffer_bottom->prev = cur_line; + + /* Create Frame Window */ + window_title(&title); + hFrameWnd = CreateWindowEx(0, szFrameClass, title.name, + WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, + CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT, + NULL,LoadMenu(beam_module,MAKEINTRESOURCE(1)), + hInstance,NULL); + free_window_title(&title); + + /* XXX OTP-5522: + The window position is not saved correctly and if the window + is closed when minimized, it's not possible to start werl again + with the window open. Temporary fix so far is to ignore saved values + and always start with initial settings. */ + /* Original: if (winPos.left == -1) { */ + /* Temporary: if (1) { */ + if (1) { + + /* initial window position */ + x = 0; + y = 0; + w = cxChar*LINE_LENGTH+FRAME_WIDTH+GetSystemMetrics(SM_CXVSCROLL); + h = cyChar*30+FRAME_HEIGHT; + } else { + /* saved window position */ + x = winPos.left; + y = winPos.top; + w = winPos.right - x; + h = winPos.bottom - y; + } + SetWindowPos(hFrameWnd, NULL, x, y, w, h, SWP_NOZORDER); + + ShowWindow(hFrameWnd, iCmdShow); + UpdateWindow(hFrameWnd); + + hAccel = LoadAccelerators(beam_module,MAKEINTRESOURCE(1)); + + ReleaseSemaphore(console_input, 1, NULL); + ReleaseSemaphore(console_output, 1, NULL); + + + /* Main message loop */ + while (GetMessage (&msg, NULL, 0, 0)) + { + if (!TranslateAccelerator(hFrameWnd,hAccel,&msg)) + { + TranslateMessage (&msg); + DispatchMessage (&msg); + } + } + /* + PostQuitMessage() results in WM_QUIT which makes GetMessage() + return 0 (which stops the main loop). Before we return from + the console thread, the ctrl_handler is called to do erl_exit. + */ + (*ctrl_handler)(CTRL_CLOSE_EVENT); + return msg.wParam; +} + +static LRESULT CALLBACK +FrameWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) +{ + RECT r; + int cy,i,bufsize; + TCHAR c; + unsigned long l; + TCHAR buf[128]; + struct title_buf title; + + switch (iMsg) { + case WM_CREATE: + /* client window creation */ + window_title(&title); + hClientWnd = CreateWindowEx(WS_EX_CLIENTEDGE, szClientClass, title.name, + WS_CHILD|WS_VISIBLE|WS_VSCROLL|WS_HSCROLL, + CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, + hwnd, (HMENU)0, hInstance, NULL); + free_window_title(&title); + hTBWnd = InitToolBar(hwnd); + UpdateWindow (hClientWnd); + return 0; + case WM_SIZE : + if (IsWindowVisible(hTBWnd)) { + SendMessage(hTBWnd,TB_AUTOSIZE,0,0L); + GetWindowRect(hTBWnd,&r); + cy = r.bottom-r.top; + } else cy = 0; + MoveWindow(hClientWnd,0,cy,LOWORD(lParam),HIWORD(lParam)-cy,TRUE); + return 0; + case WM_ERASEBKGND: + return 1; + case WM_SETFOCUS : + CreateCaret(hClientWnd, NULL, cxChar, cyChar); + SetCaretPos(GetXFromCurrentY(GetDC(hwnd),0,cur_x), (cur_y-iVscrollPos)*cyChar); + ShowCaret(hClientWnd); + return 0; + case WM_KILLFOCUS: + HideCaret(hClientWnd); + DestroyCaret(); + return 0; + case WM_INITMENUPOPUP : + if (lParam == 0) /* File popup menu */ + { + EnableMenuItem((HMENU)wParam, IDMENU_STARTLOG, + logfile ? MF_GRAYED : MF_ENABLED); + EnableMenuItem((HMENU)wParam, IDMENU_STOPLOG, + logfile ? MF_ENABLED : MF_GRAYED); + return 0; + } + else if (lParam == 1) /* Edit popup menu */ + { + EnableMenuItem((HMENU)wParam, IDMENU_COPY, + fTextSelected ? MF_ENABLED : MF_GRAYED); + EnableMenuItem((HMENU)wParam, IDMENU_PASTE, + IsClipboardFormatAvailable(CF_TEXT) ? MF_ENABLED : MF_GRAYED); + return 0; + } + else if (lParam == 3) /* View popup menu */ + { + CheckMenuItem((HMENU)wParam,IDMENU_TOOLBAR, + IsWindowVisible(hTBWnd) ? MF_CHECKED : MF_UNCHECKED); + return 0; + } + break; + case WM_NOTIFY: + switch (((LPNMHDR) lParam)->code) { + case TTN_NEEDTEXT: + { + LPTOOLTIPTEXT lpttt; + lpttt = (LPTOOLTIPTEXT) lParam; + lpttt->hinst = hInstance; + /* check for combobox handle */ + if (lpttt->uFlags&TTF_IDISHWND) { + if ((lpttt->hdr.idFrom == (UINT) hComboWnd)) { + lstrcpy(lpttt->lpszText,TEXT("Command History")); + break; + } + } + /* check for toolbar buttons */ + switch (lpttt->hdr.idFrom) { + case IDMENU_COPY: + lstrcpy(lpttt->lpszText,TEXT("Copy (Ctrl+C)")); + break; + case IDMENU_PASTE: + lstrcpy(lpttt->lpszText,TEXT("Paste (Ctrl+V)")); + break; + case IDMENU_FONT: + lstrcpy(lpttt->lpszText,TEXT("Fonts")); + break; + case IDMENU_ABOUT: + lstrcpy(lpttt->lpszText,TEXT("Help")); + break; + } + } + } + break; + case WM_COMMAND: + switch(LOWORD(wParam)) + { + case IDMENU_STARTLOG: + OpenLogFile(hwnd); + return 0; + case IDMENU_STOPLOG: + CloseLogFile(hwnd); + return 0; + case IDMENU_EXIT: + SendMessage(hwnd, WM_CLOSE, 0, 0L); + return 0; + case IDMENU_COPY: + if (fTextSelected) + OnEditCopy(hClientWnd); + return 0; + case IDMENU_PASTE: + OnEditPaste(hClientWnd); + return 0; + case IDMENU_SELALL: + OnEditSelAll(hClientWnd); + return 0; + case IDMENU_FONT: + if (ConChooseFont(hClientWnd)) { + ConSetFont(hClientWnd); + } + SaveUserPreferences(); + return 0; + case IDMENU_SELECTBKG: + ConChooseColor(hClientWnd); + SaveUserPreferences(); + return 0; + case IDMENU_TOOLBAR: + if (toolbarVisible) { + ShowWindow(hTBWnd,SW_HIDE); + toolbarVisible = FALSE; + } else { + ShowWindow(hTBWnd,SW_SHOW); + toolbarVisible = TRUE; + } + GetClientRect(hwnd,&r); + PostMessage(hwnd,WM_SIZE,0,MAKELPARAM(r.right,r.bottom)); + return 0; + case IDMENU_ABOUT: + DialogBox(beam_module,TEXT("AboutBox"),hwnd,AboutDlgProc); + return 0; + case ID_COMBOBOX: + switch (HIWORD(wParam)) { + case CBN_SELENDOK: + i = SendMessage(hComboWnd,CB_GETCURSEL,0,0); + if (i != CB_ERR) { + buf[0] = 0x01; /* CTRL+A */ + buf[1] = 0x0B; /* CTRL+K */ + bufsize = SendMessage(hComboWnd,CB_GETLBTEXT,i,(LPARAM)&buf[2]); + if (bufsize != CB_ERR) + write_inbuf(buf,bufsize+2); + SetFocus(hwnd); + } + break; + case CBN_SELENDCANCEL: + break; + } + break; + case ID_BREAK: /* CTRL+BRK */ + /* pass on break char if the ctrl_handler is disabled */ + if ((*ctrl_handler)(CTRL_C_EVENT) == FALSE) { + c = 0x03; + write_inbuf(&c,1); + } + return 0; + } + break; + case WM_KEYDOWN : + switch (wParam) { + case VK_UP: c = 'P'-'@'; break; + case VK_DOWN : c = 'N'-'@'; break; + case VK_RIGHT : c = 'F'-'@'; break; + case VK_LEFT : c = 'B'-'@'; break; + case VK_DELETE : c = 'D' -'@'; break; + case VK_HOME : c = 'A'-'@'; break; + case VK_END : c = 'E'-'@'; break; + case VK_RETURN : AddToCmdHistory(); return 0; + case VK_PRIOR : /* PageUp */ + PostMessage(hClientWnd, WM_VSCROLL, SB_PAGEUP, 0); + return 0; + case VK_NEXT : /* PageDown */ + PostMessage(hClientWnd, WM_VSCROLL, SB_PAGEDOWN, 0); + return 0; + default: return 0; + } + write_inbuf(&c, 1); + return 0; + case WM_CHAR: + c = (TCHAR)wParam; + write_inbuf(&c,1); + return 0; + case WM_CLOSE : + break; + case WM_DESTROY : + SaveUserPreferences(); + destroyed = TRUE; + PostQuitMessage(0); + return 0; + case WM_SAVE_PREFS : + SaveUserPreferences(); + return 0; + } + return DefWindowProc(hwnd, iMsg, wParam, lParam); +} + +static BOOL +Client_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct) +{ + ConFontInitialize(hwnd); + cur_x = cur_y = 0; + iVscrollPos = 0; + iHscrollPos = 0; + return TRUE; +} + +static void +Client_OnPaint(HWND hwnd) +{ + ScreenLine_t *pLine; + int x,y,i,iTop,iBot; + PAINTSTRUCT ps; + RECT rcInvalid; + HDC hdc; + + hdc = BeginPaint(hwnd, &ps); + rcInvalid = ps.rcPaint; + hdc = ps.hdc; + iTop = max(0, iVscrollPos + rcInvalid.top/cyChar); + iBot = min(nBufLines, iVscrollPos + rcInvalid.bottom/cyChar+1); + pLine = GetLineFromY(iTop); + for (i = iTop; i < iBot && pLine != NULL; i++) { + y = cyChar*(i-iVscrollPos); + x = -cxChar*iHscrollPos; + TextOut(hdc, x, y, &pLine->text[0], pLine->width); + pLine = pLine->next; + } + if (fTextSelected || fSelecting) { + InvertSelectionArea(hwnd); + } + SetCaretPos(GetXFromCurrentY(hdc,iHscrollPos,cur_x), (cur_y-iVscrollPos)*cyChar); + EndPaint(hwnd, &ps); +} +#ifdef HARDDEBUG +static void dump_linebufs(void) { + char *buff; + ScreenLine_t *s = buffer_top; + fprintf(stderr,"LinebufDump------------------------\n"); + while(s) { + if (s == buffer_top) fprintf(stderr,"BT-> "); + if (s == buffer_bottom) fprintf(stderr,"BB-> "); + if (s == cur_line) fprintf(stderr,"CL-> "); + + buff = (char *) ALLOC(s->width+1); + memcpy(buff,s->text,s->width); + buff[s->width] = '\0'; + fprintf(stderr,"{\"%s\",%d,%d}\n",buff,s->newline,s->allocated); + FREE(buff); + s = s->next; + } + fprintf(stderr,"LinebufDumpEnd---------------------\n"); + fflush(stderr); +} +#endif + +static void reorganize_linebufs(HWND hwnd) { + ScreenLine_t *otop = buffer_top; + ScreenLine_t *obot = buffer_bottom; + ScreenLine_t *next; + int i,cpos; + + cpos = 0; + i = nBufLines - cur_y; + while (i > 1) { + cpos += obot->width; + obot = obot->prev; + i--; + } + cpos += (obot->width - cur_x); +#ifdef HARDDEBUG + fprintf(stderr,"nBufLines = %d, cur_x = %d, cur_y = %d, cpos = %d\n", + nBufLines,cur_x,cur_y,cpos); + fflush(stderr); +#endif + + + nBufLines = 0; + buffer_top = cur_line = ConNewLine(); + cur_line->next = buffer_bottom = ConNewLine(); + buffer_bottom->prev = cur_line; + + cur_x = cur_y = 0; + iVscrollPos = 0; + iHscrollPos = 0; + + while(otop) { + for(i=0;i<otop->width;++i) { + cur_line->text[cur_x] = otop->text[i]; + cur_x++; + if (cur_x > cur_line->width) + cur_line->width = cur_x; + if (GetXFromCurrentY(GetDC(hwnd),0,cur_x) + cxChar > + (LINE_LENGTH * cxChar)) { + ConCarriageFeed(0); + } + } + if (otop->newline) { + ConCarriageFeed(1); + /*ConScrollScreen();*/ + } + next = otop->next; + FREE(otop->text); + FREE(otop); + otop = next; + } + while (cpos) { + cur_x--; + if (cur_x < 0) { + cur_y--; + cur_line = cur_line->prev; + cur_x = cur_line->width-1; + } + cpos--; + } + SetCaretPos(GetXFromCurrentY(GetDC(hwnd),iHscrollPos,cur_x), (cur_y-iVscrollPos)*cyChar); +#ifdef HARDDEBUG + fprintf(stderr,"canvasColumns = %d,nBufLines = %d, cur_x = %d, cur_y = %d\n", + canvasColumns,nBufLines,cur_x,cur_y); + fflush(stderr); +#endif +} + + +static void +Client_OnSize(HWND hwnd, UINT state, int cx, int cy) +{ + RECT r; + SCROLLBARINFO sbi; + int w,h,columns; + int scrollheight; + cxClient = cx; + cyClient = cy; + set_scroll_info(hwnd); + GetClientRect(hwnd,&r); + w = r.right - r.left; + h = r.bottom - r.top; + sbi.cbSize = sizeof(SCROLLBARINFO); + if (!GetScrollBarInfo(hwnd, OBJID_HSCROLL,&sbi) || + (sbi.rgstate[0] & STATE_SYSTEM_INVISIBLE)) { + scrollheight = 0; + } else { + scrollheight = sbi.rcScrollBar.bottom - sbi.rcScrollBar.top; + } + canvasRows = (h - scrollheight) / cyChar; + if (canvasRows < DEF_CANVAS_ROWS) { + canvasRows = DEF_CANVAS_ROWS; + } + columns = (w - GetSystemMetrics(SM_CXVSCROLL)) /cxChar; + if (columns < DEF_CANVAS_COLUMNS) + columns = DEF_CANVAS_COLUMNS; + if (columns != canvasColumns) { + canvasColumns = columns; + /*dump_linebufs();*/ + reorganize_linebufs(hwnd); + fSelecting = fTextSelected = FALSE; + InvalidateRect(hwnd, NULL, TRUE); +#ifdef HARDDEBUG + fprintf(stderr,"Paint: cols = %d, rows = %d\n",canvasColumns,canvasRows); + fflush(stderr); +#endif + } + + SetCaretPos(GetXFromCurrentY(GetDC(hwnd),iHscrollPos,cur_x), (cur_y-iVscrollPos)*cyChar); +} + +static void calc_charpoint_from_point(HDC dc, int x, int y, int y_offset, POINT *pt) +{ + int r; + int hscrollPix = iHscrollPos * cxChar; + + pt->y = y/cyChar + iVscrollPos + y_offset; + + if (x > (LINE_LENGTH-iHscrollPos) * cxChar) { + x = (LINE_LENGTH-iHscrollPos) * cxChar; + } + if (pt->y - y_offset > 0 && GetLineFromY(pt->y - y_offset) == NULL) { + pt->y = nBufLines - 1 + y_offset; + pt->x = GetLineFromY(pt->y - y_offset)->width; + } else { + for (pt->x = 1; + (r = GetXFromLine(dc, 0, pt->x, GetLineFromY(pt->y - y_offset))) != 0 && + (r - hscrollPix) < x; + ++(pt->x)) + ; + if ((r - hscrollPix) > x) + --(pt->x); +#ifdef HARD_SEL_DEBUG + fprintf(stderr,"pt->x = %d, iHscrollPos = %d\n",(int) pt->x, iHscrollPos); + fflush(stderr); +#endif + if (pt->x <= 0) { + pt->x = x/cxChar + iHscrollPos; + } + } +} + + +static void +Client_OnLButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags) +{ + int r; + SetFocus(GetParent(hwnd)); /* In case combobox steals the focus */ +#ifdef HARD_SEL_DEBUG + fprintf(stderr,"OnLButtonDown fSelecting = %d, fTextSelected = %d:\n", + fSelecting,fTextSelected); + fflush(stderr); +#endif + if (fTextSelected) { + InvertSelectionArea(hwnd); + } + fTextSelected = FALSE; + + calc_charpoint_from_point(GetDC(hwnd), x, y, 0, &editBeg); + + editEnd.x = editBeg.x; + editEnd.y = editBeg.y + 1; + fSelecting = TRUE; + SetCapture(hwnd); +} + +static void +Client_OnRButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags) +{ + if (fTextSelected) { + fSelecting = TRUE; + Client_OnMouseMove(hwnd,x,y,keyFlags); + fSelecting = FALSE; + } +} + +static void +Client_OnLButtonUp(HWND hwnd, int x, int y, UINT keyFlags) +{ +#ifdef HARD_SEL_DEBUG + fprintf(stderr,"OnLButtonUp fSelecting = %d, fTextSelected = %d:\n", + fSelecting,fTextSelected); + fprintf(stderr,"(Beg.x = %d, Beg.y = %d, " + "End.x = %d, End.y = %d)\n",editBeg.x,editBeg.y, + editEnd.x,editEnd.y); +#endif + if (fSelecting && + !(editBeg.x == editEnd.x && editBeg.y == (editEnd.y - 1))) { + fTextSelected = TRUE; + } +#ifdef HARD_SEL_DEBUG + fprintf(stderr,"OnLButtonUp fTextSelected = %d:\n", + fTextSelected); + fflush(stderr); +#endif + fSelecting = FALSE; + ReleaseCapture(); +} + +#define EMPTY_RECT(R) \ +(((R).bottom - (R).top == 0) || ((R).right - (R).left == 0)) +#define ABS(X) (((X)< 0) ? -1 * (X) : X) +#define DIFF(A,B) ABS(((int)(A)) - ((int)(B))) + +static int diff_sel_area(RECT old[3], RECT new[3], RECT result[6]) +{ + int absposold = old[0].left + old[0].top * canvasColumns; + int absposnew = new[0].left + new[0].top * canvasColumns; + int absendold = absposold, absendnew = absposnew; + int i, x, ret = 0; + int abspos[2],absend[2]; + for(i = 0; i < 3; ++i) { + if (!EMPTY_RECT(old[i])) { + absendold += (old[i].right - old[i].left) * + (old[i].bottom - old[i].top); + } + if (!EMPTY_RECT(new[i])) { + absendnew += (new[i].right - new[i].left) * + (new[i].bottom - new[i].top); + } + } + abspos[0] = min(absposold, absposnew); + absend[0] = DIFF(absposold, absposnew) + abspos[0]; + abspos[1] = min(absendold, absendnew); + absend[1] = DIFF(absendold, absendnew) + abspos[1]; +#ifdef HARD_SEL_DEBUG + fprintf(stderr,"abspos[0] = %d, absend[0] = %d, abspos[1] = %d, absend[1] = %d\n",abspos[0],absend[0],abspos[1],absend[1]); + fflush(stderr); +#endif + i = 0; + for (x = 0; x < 2; ++x) { + if (abspos[x] != absend[x]) { + int consumed = 0; + result[i].left = abspos[x] % canvasColumns; + result[i].top = abspos[x] / canvasColumns; + result[i].bottom = result[i].top + 1; + if ((absend[x] - abspos[x]) + result[i].left < canvasColumns) { +#ifdef HARD_SEL_DEBUG + fprintf(stderr,"Nowrap, %d < canvasColumns\n", + (absend[x] - abspos[x]) + result[i].left); + fflush(stderr); +#endif + result[i].right = (absend[x] - abspos[x]) + result[i].left; + consumed += result[i].right - result[i].left; + } else { +#ifdef HARD_SEL_DEBUG + fprintf(stderr,"Wrap, %d >= canvasColumns\n", + (absend[x] - abspos[x]) + result[i].left); + fflush(stderr); +#endif + result[i].right = canvasColumns; + consumed += result[i].right - result[i].left; + if (absend[x] - abspos[x] - consumed >= canvasColumns) { + ++i; + result[i].top = result[i-1].bottom; + result[i].left = 0; + result[i].right = canvasColumns; + result[i].bottom = (absend[x] - abspos[x] - consumed) / canvasColumns + result[i].top; + consumed += (result[i].bottom - result[i].top) * canvasColumns; + } + if (absend[x] - abspos[x] - consumed > 0) { + ++i; + result[i].top = result[i-1].bottom; + result[i].bottom = result[i].top + 1; + result[i].left = 0; + result[i].right = absend[x] - abspos[x] - consumed; + } + } + ++i; + } + } +#ifdef HARD_SEL_DEBUG + if (i > 2) { + int x; + fprintf(stderr,"i = %d\n",i); + fflush(stderr); + for (x = 0; x < i; ++x) { + fprintf(stderr, "result[%d]: top = %d, left = %d, " + "bottom = %d. right = %d\n", + x, result[x].top, result[x].left, + result[x].bottom, result[x].right); + } + } +#endif + return i; +} + + + +static void calc_sel_area(RECT rects[3], POINT beg, POINT end) +{ + /* These are not really rects and points, these are character + based positions, need to be multiplied by cxChar and cyChar to + make up canvas coordinates */ + memset(rects,0,3*sizeof(RECT)); + rects[0].left = beg.x; + rects[0].top = beg.y; + rects[0].bottom = beg.y+1; + if (end.y - beg.y == 1) { /* Only one row */ + rects[0].right = end.x; + goto out; + } + rects[0].right = canvasColumns; + if (end.y - beg.y > 2) { + rects[1].left = 0; + rects[1].top = rects[0].bottom; + rects[1].right = canvasColumns; + rects[1].bottom = end.y - 1; + } + rects[2].left = 0; + rects[2].top = end.y - 1; + rects[2].bottom = end.y; + rects[2].right = end.x; + + out: +#ifdef HARD_SEL_DEBUG + { + int i; + fprintf(stderr,"beg.x = %d, beg.y = %d, end.x = %d, end.y = %d\n", + beg.x,beg.y,end.x,end.y); + for (i = 0; i < 3; ++i) { + fprintf(stderr,"[%d] left = %d, top = %d, " + "right = %d, bottom = %d\n", + i, rects[i].left, rects[i].top, + rects[i].right, rects[i].bottom); + } + fflush(stderr); + } +#endif + return; +} + +static void calc_sel_area_turned(RECT rects[3], POINT eBeg, POINT eEnd) { + POINT from,to; + if (eBeg.y >= eEnd.y || + (eBeg.y == eEnd.y - 1 && eBeg.x > eEnd.x)) { +#ifdef HARD_SEL_DEBUG + fprintf(stderr,"Reverting (Beg.x = %d, Beg.y = %d, " + "End.x = %d, End.y = %d)\n",eBeg.x,eBeg.y, + eEnd.x,eEnd.y); + fflush(stderr); +#endif + from.x = eEnd.x; + from.y = eEnd.y - 1; + to.x = eBeg.x; + to.y = eBeg.y + 1; + calc_sel_area(rects,from,to); + } else { + calc_sel_area(rects,eBeg,eEnd); + } +} + + +static void InvertSelectionArea(HWND hwnd) +{ + RECT rects[3]; + POINT from,to; + int i; + calc_sel_area_turned(rects,editBeg,editEnd); + for (i = 0; i < 3; ++i) { + if (!EMPTY_RECT(rects[i])) { + from.x = rects[i].left; + to.x = rects[i].right; + from.y = rects[i].top; + to.y = rects[i].bottom; + DrawSelection(hwnd,from,to); + } + } +} + +static void +Client_OnMouseMove(HWND hwnd, int x, int y, UINT keyFlags) +{ + if (fSelecting) { + RECT rold[3], rnew[3], rupdate[6]; + int num_updates,i,r; + POINT from,to; + calc_sel_area_turned(rold,editBeg,editEnd); + + calc_charpoint_from_point(GetDC(hwnd), x, y, 1, &editEnd); + + calc_sel_area_turned(rnew,editBeg,editEnd); + num_updates = diff_sel_area(rold,rnew,rupdate); + for (i = 0; i < num_updates;++i) { + from.x = rupdate[i].left; + to.x = rupdate[i].right; + from.y = rupdate[i].top; + to.y = rupdate[i].bottom; +#ifdef HARD_SEL_DEBUG + fprintf(stderr,"from: x=%d,y=%d, to: x=%d, y=%d\n", + from.x, from.y,to.x,to.y); + fflush(stderr); +#endif + DrawSelection(hwnd,from,to); + } + } +} + +static void +Client_OnVScroll(HWND hwnd, HWND hwndCtl, UINT code, int pos) +{ + int iVscroll; + + switch(code) { + case SB_LINEDOWN: + iVscroll = 1; + break; + case SB_LINEUP: + iVscroll = -1; + break; + case SB_PAGEDOWN: + iVscroll = max(1, cyClient/cyChar); + break; + case SB_PAGEUP: + iVscroll = min(-1, -cyClient/cyChar); + break; + case SB_THUMBTRACK: + iVscroll = pos - iVscrollPos; + break; + default: + iVscroll = 0; + } + iVscroll = max(-iVscrollPos, min(iVscroll, iVscrollMax-iVscrollPos)); + if (iVscroll != 0) { + iVscrollPos += iVscroll; + ScrollWindowEx(hwnd, 0, -cyChar*iVscroll, NULL, NULL, + NULL, NULL, SW_ERASE | SW_INVALIDATE); + SetScrollPos(hwnd, SB_VERT, iVscrollPos, TRUE); + iVscroll = GetScrollPos(hwnd, SB_VERT); + UpdateWindow(hwnd); + } +} + +static void +Client_OnHScroll(HWND hwnd, HWND hwndCtl, UINT code, int pos) +{ + int iHscroll, curCharWidth = cxClient/cxChar; + + switch(code) { + case SB_LINEDOWN: + iHscroll = 1; + break; + case SB_LINEUP: + iHscroll = -1; + break; + case SB_PAGEDOWN: + iHscroll = max(1,curCharWidth-1); + break; + case SB_PAGEUP: + iHscroll = min(-1,-(curCharWidth-1)); + break; + case SB_THUMBTRACK: + iHscroll = pos - iHscrollPos; + break; + default: + iHscroll = 0; + } + iHscroll = max(-iHscrollPos, min(iHscroll, iHscrollMax-iHscrollPos-(curCharWidth-1))); + if (iHscroll != 0) { + iHscrollPos += iHscroll; + ScrollWindow(hwnd, -cxChar*iHscroll, 0, NULL, NULL); + SetScrollPos(hwnd, SB_HORZ, iHscrollPos, TRUE); + UpdateWindow(hwnd); + } +} + +static LRESULT CALLBACK +ClientWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) +{ + switch (iMsg) { + HANDLE_MSG(hwnd, WM_CREATE, Client_OnCreate); + HANDLE_MSG(hwnd, WM_SIZE, Client_OnSize); + HANDLE_MSG(hwnd, WM_PAINT, Client_OnPaint); + HANDLE_MSG(hwnd, WM_LBUTTONDOWN, Client_OnLButtonDown); + HANDLE_MSG(hwnd, WM_RBUTTONDOWN, Client_OnRButtonDown); + HANDLE_MSG(hwnd, WM_LBUTTONUP, Client_OnLButtonUp); + HANDLE_MSG(hwnd, WM_MOUSEMOVE, Client_OnMouseMove); + HANDLE_MSG(hwnd, WM_VSCROLL, Client_OnVScroll); + HANDLE_MSG(hwnd, WM_HSCROLL, Client_OnHScroll); + case WM_CONBEEP: + if (0) Beep(440, 400); + return 0; + case WM_CONTEXT: + ConDrawText(hwnd); + return 0; + case WM_CLOSE: + break; + case WM_DESTROY: + PostQuitMessage(0); + return 0; + } + return DefWindowProc (hwnd, iMsg, wParam, lParam); +} + +static void +LoadUserPreferences(void) +{ + DWORD size; + DWORD res; + DWORD type; + + /* default prefs */ + GetObject(GetStockObject(SYSTEM_FIXED_FONT),sizeof(LOGFONT),(PSTR)&logfont); + fgColor = GetSysColor(COLOR_WINDOWTEXT); + bkgColor = GetSysColor(COLOR_WINDOW); + winPos.left = -1; + toolbarVisible = TRUE; + + if (RegCreateKeyEx(HKEY_CURRENT_USER, USER_KEY, 0, 0, + REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, + &key, &res) != ERROR_SUCCESS) + return; + has_key = TRUE; + if (res == REG_CREATED_NEW_KEY) + return; + size = sizeof(logfont); + res = RegQueryValueEx(key,TEXT("Font"),NULL,&type,(LPBYTE)&logfont,&size); + size = sizeof(fgColor); + res = RegQueryValueEx(key,TEXT("FgColor"),NULL,&type,(LPBYTE)&fgColor,&size); + size = sizeof(bkgColor); + res = RegQueryValueEx(key,TEXT("BkColor"),NULL,&type,(LPBYTE)&bkgColor,&size); + size = sizeof(winPos); + res = RegQueryValueEx(key,TEXT("Pos"),NULL,&type,(LPBYTE)&winPos,&size); + size = sizeof(toolbarVisible); + res = RegQueryValueEx(key,TEXT("Toolbar"),NULL,&type,(LPBYTE)&toolbarVisible,&size); +} + +static void +SaveUserPreferences(void) +{ + WINDOWPLACEMENT wndPlace; + + if (has_key == TRUE) { + RegSetValueEx(key,TEXT("Font"),0,REG_BINARY,(CONST BYTE *)&logfont,sizeof(LOGFONT)); + RegSetValueEx(key,TEXT("FgColor"),0,REG_DWORD,(CONST BYTE *)&fgColor,sizeof(fgColor)); + RegSetValueEx(key,TEXT("BkColor"),0,REG_DWORD,(CONST BYTE *)&bkgColor,sizeof(bkgColor)); + RegSetValueEx(key,TEXT("Toolbar"),0,REG_DWORD,(CONST BYTE *)&toolbarVisible,sizeof(toolbarVisible)); + + wndPlace.length = sizeof(WINDOWPLACEMENT); + GetWindowPlacement(hFrameWnd,&wndPlace); + /* If wndPlace.showCmd == SW_MINIMIZE, then the window is minimized. + We don't care, wndPlace.rcNormalPosition always holds the last known position. */ + winPos = wndPlace.rcNormalPosition; + RegSetValueEx(key,TEXT("Pos"),0,REG_BINARY,(CONST BYTE *)&winPos,sizeof(winPos)); + } +} + + +static void +set_scroll_info(HWND hwnd) +{ + SCROLLINFO info; + int hScrollBy; + /* + * Set vertical scrolling range and scroll box position. + */ + + iVscrollMax = nBufLines-1; + iVscrollPos = min(iVscrollPos, iVscrollMax); + info.cbSize = sizeof(info); + info.fMask = SIF_PAGE|SIF_RANGE|SIF_POS; + info.nMin = 0; + info.nPos = iVscrollPos; + info.nPage = min(cyClient/cyChar, iVscrollMax); + info.nMax = iVscrollMax; + SetScrollInfo(hwnd, SB_VERT, &info, TRUE); + + /* + * Set horizontal scrolling range and scroll box position. + */ + + iHscrollMax = LINE_LENGTH-1; + hScrollBy = max(0, (iHscrollPos - (iHscrollMax-cxClient/cxChar))*cxChar); + iHscrollPos = min(iHscrollPos, iHscrollMax); + info.nPos = iHscrollPos; + info.nPage = cxClient/cxChar; + info.nMax = iHscrollMax; + SetScrollInfo(hwnd, SB_HORZ, &info, TRUE); + /*ScrollWindow(hwnd, hScrollBy, 0, NULL, NULL);*/ +} + + +static void +ensure_line_below(void) +{ + if (cur_line->next == NULL) { + if (nBufLines >= lines_to_save) { + ScreenLine_t* pLine = buffer_top->next; + FREE(buffer_top->text); + FREE(buffer_top); + buffer_top = pLine; + buffer_top->prev = NULL; + nBufLines--; + } + cur_line->next = ConNewLine(); + cur_line->next->prev = cur_line; + buffer_bottom = cur_line->next; + set_scroll_info(hClientWnd); + } +} + +static ScreenLine_t* +ConNewLine(void) +{ + ScreenLine_t *pLine; + + pLine = (ScreenLine_t *)ALLOC(sizeof(ScreenLine_t)); + if (!pLine) + return NULL; + pLine->text = (TCHAR *) ALLOC(canvasColumns * sizeof(TCHAR)); +#ifdef HARDDEBUG + pLine->allocated = canvasColumns; +#endif + pLine->width = 0; + pLine->prev = pLine->next = NULL; + pLine->newline = 0; + nBufLines++; + return pLine; +} + +static ScreenLine_t* +GetLineFromY(int y) +{ + ScreenLine_t *pLine = buffer_top; + int i; + + for (i = 0; i < nBufLines && pLine != NULL; i++) { + if (i == y) + return pLine; + pLine = pLine->next; + } + return NULL; +} + +void ConCarriageFeed(int hard_newline) +{ + cur_x = 0; + ensure_line_below(); + cur_line->newline = hard_newline; + cur_line = cur_line->next; + if (cur_y < nBufLines-1) { + cur_y++; + } else if (iVscrollPos > 0) { + iVscrollPos--; + } +} + +/* + * Scroll screen if cursor is not visible. + */ +static void +ConScrollScreen(void) +{ + if (cur_y >= iVscrollPos + cyClient/cyChar) { + int iVscroll; + + iVscroll = cur_y - iVscrollPos - cyClient/cyChar + 1; + iVscrollPos += iVscroll; + ScrollWindowEx(hClientWnd, 0, -cyChar*iVscroll, NULL, NULL, + NULL, NULL, SW_ERASE | SW_INVALIDATE); + SetScrollPos(hClientWnd, SB_VERT, iVscrollPos, TRUE); + UpdateWindow(hClientWnd); + } +} + +static void +DrawSelection(HWND hwnd, POINT pt1, POINT pt2) +{ + HDC hdc; + int width,height; +#ifdef HARD_SEL_DEBUG + fprintf(stderr,"pt1.x = %d, pt1.y = %d, pt2.x = %d, pt2.y = %d\n", + (int) pt1.x, (int) pt1.y, (int) pt2.x, (int) pt2.y); +#endif + pt1.x = GetXFromLine(GetDC(hwnd),iHscrollPos,pt1.x,GetLineFromY(pt1.y)); + pt2.x = GetXFromLine(GetDC(hwnd),iHscrollPos,pt2.x,GetLineFromY(pt2.y-1)); + pt1.y -= iVscrollPos; + pt2.y -= iVscrollPos; + pt1.y *= cyChar; + pt2.y *= cyChar; +#ifdef HARD_SEL_DEBUG + fprintf(stderr,"pt1.x = %d, pt1.y = %d, pt2.x = %d, pt2.y = %d\n", + (int) pt1.x, (int) pt1.y, (int) pt2.x, (int) pt2.y); + fflush(stderr); +#endif + width = pt2.x-pt1.x; + height = pt2.y - pt1.y; + hdc = GetDC(hwnd); + PatBlt(hdc,pt1.x,pt1.y,width,height,DSTINVERT); + ReleaseDC(hwnd,hdc); +} + +static void +OnEditCopy(HWND hwnd) +{ + HGLOBAL hMem; + TCHAR *pMem; + ScreenLine_t *pLine; + RECT rects[3]; + POINT from,to; + int i,j,sum,len; + if (editBeg.y >= editEnd.y || + (editBeg.y == editEnd.y - 1 && editBeg.x > editEnd.x)) { +#ifdef HARD_SEL_DEBUG + fprintf(stderr,"CopyReverting (Beg.x = %d, Beg.y = %d, " + "End.x = %d, End.y = %d)\n",editBeg.x,editBeg.y, + editEnd.x,editEnd.y); + fflush(stderr); +#endif + from.x = editEnd.x; + from.y = editEnd.y - 1; + to.x = editBeg.x; + to.y = editBeg.y + 1; + calc_sel_area(rects,from,to); + } else { + calc_sel_area(rects,editBeg,editEnd); + } + sum = 1; + for (i = 0; i < 3; ++i) { + if (!EMPTY_RECT(rects[i])) { + pLine = GetLineFromY(rects[i].top); + for (j = rects[i].top; j < rects[i].bottom ;++j) { + if (pLine == NULL) { + sum += 2; + break; + } + if (pLine->width > rects[i].left) { + sum += (pLine->width < rects[i].right) ? + pLine->width - rects[i].left : + rects[i].right - rects[i].left; + } + if(pLine->newline && rects[i].right >= pLine->width) { + sum += 2; + } + pLine = pLine->next; + } + } + } +#ifdef HARD_SEL_DEBUG + fprintf(stderr,"sum = %d\n",sum); + fflush(stderr); +#endif + hMem = GlobalAlloc(GHND, sum * sizeof(TCHAR)); + pMem = GlobalLock(hMem); + for (i = 0; i < 3; ++i) { + if (!EMPTY_RECT(rects[i])) { + pLine = GetLineFromY(rects[i].top); + for (j = rects[i].top; j < rects[i].bottom; ++j) { + if (pLine == NULL) { + memcpy(pMem,TEXT("\r\n"),2 * sizeof(TCHAR)); + pMem += 2; + break; + } + if (pLine->width > rects[i].left) { + len = (pLine->width < rects[i].right) ? + pLine->width - rects[i].left : + rects[i].right - rects[i].left; + memcpy(pMem,pLine->text + rects[i].left,len * sizeof(TCHAR)); + pMem +=len; + } + if(pLine->newline && rects[i].right >= pLine->width) { + memcpy(pMem,TEXT("\r\n"),2 * sizeof(TCHAR)); + pMem += 2; + } + pLine = pLine->next; + } + } + } + *pMem = TEXT('\0'); + /* Flash de selection area to give user feedback about copying */ + InvertSelectionArea(hwnd); + Sleep(100); + InvertSelectionArea(hwnd); + + OpenClipboard(hwnd); + EmptyClipboard(); + GlobalUnlock(hMem); + SetClipboardData(CF_UNICODETEXT,hMem); + CloseClipboard(); +} + +/* XXX:PaN Tchar or char? */ +static void +OnEditPaste(HWND hwnd) +{ + HANDLE hClipMem; + TCHAR *pClipMem,*pMem,*pMem2; + if (!OpenClipboard(hwnd)) + return; + if ((hClipMem = GetClipboardData(CF_UNICODETEXT)) != NULL) { + pClipMem = GlobalLock(hClipMem); + pMem = (TCHAR *)ALLOC(GlobalSize(hClipMem) * sizeof(TCHAR)); + pMem2 = pMem; + while ((*pMem2 = *pClipMem) != TEXT('\0')) { + if (*pClipMem == TEXT('\r')) + *pMem2 = TEXT('\n'); + ++pMem2; + ++pClipMem; + } + GlobalUnlock(hClipMem); + write_inbuf(pMem, _tcsclen(pMem)); + } + CloseClipboard(); +} + +static void +OnEditSelAll(HWND hwnd) +{ + editBeg.x = 0; + editBeg.y = 0; + editEnd.x = LINE_LENGTH-1; + editEnd.y = cur_y; + fTextSelected = TRUE; + InvalidateRect(hwnd, NULL, TRUE); +} + +UINT APIENTRY CFHookProc(HWND hDlg,UINT iMsg,WPARAM wParam,LPARAM lParam) +{ + /* Hook procedure for font dialog box */ + HWND hOwner; + RECT rc,rcOwner,rcDlg; + switch (iMsg) { + case WM_INITDIALOG: + /* center dialogbox within its owner window */ + if ((hOwner = GetParent(hDlg)) == NULL) + hOwner = GetDesktopWindow(); + GetWindowRect(hOwner, &rcOwner); + GetWindowRect(hDlg, &rcDlg); + CopyRect(&rc, &rcOwner); + OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top); + OffsetRect(&rc, -rc.left, -rc.top); + OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom); + SetWindowPos(hDlg,HWND_TOP,rcOwner.left + (rc.right / 2), + rcOwner.top + (rc.bottom / 2),0,0,SWP_NOSIZE); + return 1; + default: + break; + } + return 0; /* Let the default procedure process the message */ +} + +static BOOL +ConChooseFont(HWND hwnd) +{ + HDC hdc; + hdc = GetDC(hwnd); + cf.lStructSize = sizeof(CHOOSEFONT); + cf.hwndOwner = hwnd; + cf.hDC = NULL; + cf.lpLogFont = &logfont; + cf.iPointSize = 0; + cf.Flags = CF_INITTOLOGFONTSTRUCT|CF_SCREENFONTS|CF_FIXEDPITCHONLY|CF_EFFECTS|CF_ENABLEHOOK; + cf.rgbColors = GetTextColor(hdc); + cf.lCustData = 0L; + cf.lpfnHook = CFHookProc; + cf.lpTemplateName = NULL; + cf.hInstance = NULL; + cf.lpszStyle = NULL; + cf.nFontType = 0; + cf.nSizeMin = 0; + cf.nSizeMax = 0; + ReleaseDC(hwnd,hdc); + return ChooseFont(&cf); +} + +static void +ConFontInitialize(HWND hwnd) +{ + HDC hdc; + TEXTMETRIC tm; + HFONT hFont; + + hFont = CreateFontIndirect(&logfont); + hdc = GetDC(hwnd); + SelectObject(hdc, hFont); + SetTextColor(hdc,fgColor); + SetBkColor(hdc,bkgColor); + GetTextMetrics(hdc, &tm); + cxChar = tm.tmAveCharWidth; + cxCharMax = tm.tmMaxCharWidth; + cyChar = tm.tmHeight + tm.tmExternalLeading; + ReleaseDC(hwnd, hdc); +} + +static void +ConSetFont(HWND hwnd) +{ + HDC hdc; + TEXTMETRIC tm; + HFONT hFontNew; + + hFontNew = CreateFontIndirect(&logfont); + SendMessage(hComboWnd,WM_SETFONT,(WPARAM)hFontNew, + MAKELPARAM(1,0)); + hdc = GetDC(hwnd); + DeleteObject(SelectObject(hdc, hFontNew)); + GetTextMetrics(hdc, &tm); + cxChar = tm.tmAveCharWidth; + cxCharMax = tm.tmMaxCharWidth; + cyChar = tm.tmHeight + tm.tmExternalLeading; + fgColor = cf.rgbColors; + SetTextColor(hdc,fgColor); + ReleaseDC(hwnd, hdc); + set_scroll_info(hwnd); + HideCaret(hwnd); + if (DestroyCaret()) { + CreateCaret(hwnd, NULL, cxChar, cyChar); + SetCaretPos(GetXFromCurrentY(hdc,iHscrollPos,cur_x), (cur_y-iVscrollPos)*cyChar); + } + ShowCaret(hwnd); + InvalidateRect(hwnd, NULL, TRUE); +} + +UINT APIENTRY +CCHookProc(HWND hDlg,UINT iMsg,WPARAM wParam,LPARAM lParam) +{ + /* Hook procedure for choose color dialog box */ + HWND hOwner; + RECT rc,rcOwner,rcDlg; + switch (iMsg) { + case WM_INITDIALOG: + /* center dialogbox within its owner window */ + if ((hOwner = GetParent(hDlg)) == NULL) + hOwner = GetDesktopWindow(); + GetWindowRect(hOwner, &rcOwner); + GetWindowRect(hDlg, &rcDlg); + CopyRect(&rc, &rcOwner); + OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top); + OffsetRect(&rc, -rc.left, -rc.top); + OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom); + SetWindowPos(hDlg,HWND_TOP,rcOwner.left + (rc.right / 2), + rcOwner.top + (rc.bottom / 2),0,0,SWP_NOSIZE); + return 1; + default: + break; + } + return 0; /* Let the default procedure process the message */ +} + +void ConChooseColor(HWND hwnd) +{ + CHOOSECOLOR cc; + static COLORREF acrCustClr[16]; + HBRUSH hbrush; + HDC hdc; + + /* Initialize CHOOSECOLOR */ + ZeroMemory(&cc, sizeof(CHOOSECOLOR)); + cc.lStructSize = sizeof(CHOOSECOLOR); + cc.hwndOwner = hwnd; + cc.lpCustColors = (LPDWORD) acrCustClr; + cc.rgbResult = bkgColor; + cc.lpfnHook = CCHookProc; + cc.Flags = CC_FULLOPEN|CC_RGBINIT|CC_SOLIDCOLOR|CC_ENABLEHOOK; + + if (ChooseColor(&cc)==TRUE) { + bkgColor = cc.rgbResult; + hdc = GetDC(hwnd); + SetBkColor(hdc,bkgColor); + ReleaseDC(hwnd,hdc); + hbrush = CreateSolidBrush(bkgColor); + DeleteObject((HBRUSH)SetClassLong(hClientWnd,GCL_HBRBACKGROUND,(LONG)hbrush)); + InvalidateRect(hwnd,NULL,TRUE); + } +} + +UINT APIENTRY OFNHookProc(HWND hwndDlg,UINT iMsg,WPARAM wParam,LPARAM lParam) +{ + /* Hook procedure for open file dialog box */ + HWND hOwner,hDlg; + RECT rc,rcOwner,rcDlg; + hDlg = GetParent(hwndDlg); + switch (iMsg) { + case WM_INITDIALOG: + /* center dialogbox within its owner window */ + if ((hOwner = GetParent(hDlg)) == NULL) + hOwner = GetDesktopWindow(); + GetWindowRect(hOwner, &rcOwner); + GetWindowRect(hDlg, &rcDlg); + CopyRect(&rc, &rcOwner); + OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top); + OffsetRect(&rc, -rc.left, -rc.top); + OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom); + SetWindowPos(hDlg,HWND_TOP,rcOwner.left + (rc.right / 2), + rcOwner.top + (rc.bottom / 2),0,0,SWP_NOSIZE); + return 1; + default: + break; + } + return 0; /* the let default procedure process the message */ +} + +static void +GetFileName(HWND hwnd, TCHAR *pFile) +{ + /* Open the File Open dialog box and */ + /* retrieve the file name */ + OPENFILENAME ofn; + TCHAR szFilterSpec [128] = TEXT("logfiles (*.log)\0*.log\0All files (*.*)\0*.*\0\0"); + #define MAXFILENAME 256 + TCHAR szFileName[MAXFILENAME]; + TCHAR szFileTitle[MAXFILENAME]; + + /* these need to be filled in */ + _tcscpy(szFileName, TEXT("erlshell.log")); + _tcscpy(szFileTitle, TEXT("")); /* must be NULL */ + + ofn.lStructSize = sizeof(OPENFILENAME); + ofn.hwndOwner = NULL; + ofn.lpstrFilter = szFilterSpec; + ofn.lpstrCustomFilter = NULL; + ofn.nMaxCustFilter = 0; + ofn.nFilterIndex = 0; + ofn.lpstrFile = szFileName; + ofn.nMaxFile = MAXFILENAME; + ofn.lpstrInitialDir = NULL; + ofn.lpstrFileTitle = szFileTitle; + ofn.nMaxFileTitle = MAXFILENAME; + ofn.lpstrTitle = TEXT("Open logfile"); + ofn.lpstrDefExt = TEXT("log"); + ofn.Flags = OFN_CREATEPROMPT|OFN_HIDEREADONLY|OFN_EXPLORER|OFN_ENABLEHOOK|OFN_NOCHANGEDIR; /* OFN_NOCHANGEDIR only works in Vista :( */ + ofn.lpfnHook = OFNHookProc; + + if (!GetOpenFileName ((LPOPENFILENAME)&ofn)){ + *pFile = TEXT('\0'); + } else { + _tcscpy(pFile, ofn.lpstrFile); + } +} + +void OpenLogFile(HWND hwnd) +{ + /* open a file for logging */ + TCHAR filename[_MAX_PATH]; + + GetFileName(hwnd, filename); + if (filename[0] == '\0') + return; + if (NULL == (logfile = _tfopen(filename,TEXT("w,ccs=UNICODE")))) + return; +} + +void CloseLogFile(HWND hwnd) +{ + /* close log file */ + fclose(logfile); + logfile = NULL; +} + +void LogFileWrite(TCHAR *buf, int num_chars) +{ + /* write to logfile */ + int from,to; + while (num_chars-- > 0) { + switch (*buf) { + case SET_CURSOR: + buf++; + from = *((int *)buf); + buf += sizeof(int)/sizeof(TCHAR); + to = *((int *)buf); + buf += (sizeof(int)/sizeof(TCHAR))-1; + num_chars -= 2 * (sizeof(int)/sizeof(TCHAR)); + // Wont seek in Unicode file, sorry... + // fseek(logfile,to-from *sizeof(TCHAR),SEEK_CUR); + break; + default: + _fputtc(*buf,logfile); + break; + } + buf++; + } +} + +static void +init_buffers(void) +{ + inbuf.data = (TCHAR *) ALLOC(BUFSIZE * sizeof(TCHAR)); + outbuf.data = (TCHAR *) ALLOC(BUFSIZE * sizeof(TCHAR)); + inbuf.size = BUFSIZE; + inbuf.rdPos = inbuf.wrPos = 0; + outbuf.size = BUFSIZE; + outbuf.rdPos = outbuf.wrPos = 0; +} + +static int +check_realloc(buffer_t *buf, int num_chars) +{ + if (buf->wrPos + num_chars >= buf->size) { + if (buf->size > MAXBUFSIZE) + return 0; + buf->size += num_chars + BUFSIZE; + if (!(buf->data = (TCHAR *)REALLOC(buf->data, buf->size * sizeof(TCHAR)))) { + buf->size = buf->rdPos = buf->wrPos = 0; + return 0; + } + } + return 1; +} + +static int +write_inbuf(TCHAR *data, int num_chars) +{ + TCHAR *buf; + int nwrite; + WaitForSingleObject(console_input,INFINITE); + if (!check_realloc(&inbuf,num_chars)) { + ReleaseSemaphore(console_input,1,NULL); + return -1; + } + buf = &inbuf.data[inbuf.wrPos]; + inbuf.wrPos += num_chars; + nwrite = num_chars; + while (nwrite--) + *buf++ = *data++; + SetEvent(console_input_event); + ReleaseSemaphore(console_input,1,NULL); + return num_chars; +} + +static int +write_outbuf(TCHAR *data, int num_chars) +{ + TCHAR *buf; + int nwrite; + + WaitForSingleObject(console_output,INFINITE); + if (!check_realloc(&outbuf, num_chars)) { + ReleaseSemaphore(console_output,1,NULL); + return -1; + } + if (outbuf.rdPos == outbuf.wrPos) + PostMessage(hClientWnd, WM_CONTEXT, 0L, 0L); + buf = &outbuf.data[outbuf.wrPos]; + outbuf.wrPos += num_chars; + nwrite = num_chars; + while (nwrite--) + *buf++ = *data++; + ReleaseSemaphore(console_output,1,NULL); + return num_chars; +} + +BOOL CALLBACK AboutDlgProc(HWND hDlg, UINT iMsg, WPARAM wParam, LPARAM lParam) +{ + HWND hOwner; + RECT rc,rcOwner,rcDlg; + + switch (iMsg) { + case WM_INITDIALOG: + /* center dialogbox within its owner window */ + if ((hOwner = GetParent(hDlg)) == NULL) + hOwner = GetDesktopWindow(); + GetWindowRect(hOwner, &rcOwner); + GetWindowRect(hDlg, &rcDlg); + CopyRect(&rc, &rcOwner); + OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top); + OffsetRect(&rc, -rc.left, -rc.top); + OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom); + SetWindowPos(hDlg,HWND_TOP,rcOwner.left + (rc.right / 2), + rcOwner.top + (rc.bottom / 2),0,0,SWP_NOSIZE); + SetDlgItemText(hDlg, ID_VERSIONSTRING, + TEXT("Erlang emulator version ") TEXT(ERLANG_VERSION)); + return TRUE; + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + case IDCANCEL: + EndDialog(hDlg,0); + return TRUE; + } + break; + } + return FALSE; +} + +static void +ConDrawText(HWND hwnd) +{ + int num_chars; + int nchars; + TCHAR *buf; + int from, to; + int dl; + int dc; + RECT rc; + + WaitForSingleObject(console_output, INFINITE); + nchars = 0; + num_chars = outbuf.wrPos - outbuf.rdPos; + buf = &outbuf.data[outbuf.rdPos]; + if (logfile != NULL) + LogFileWrite(buf, num_chars); + + +#ifdef HARDDEBUG + { + TCHAR *bu = (TCHAR *) ALLOC((num_chars+1) * sizeof(TCHAR)); + memcpy(bu,buf,num_chars * sizeof(TCHAR)); + bu[num_chars]='\0'; + fprintf(stderr,TEXT("ConDrawText\"%s\"\n"),bu); + FREE(bu); + fflush(stderr); + } +#endif + /* + * Don't draw any text in the window; just update the line buffers + * and invalidate the appropriate part of the window. The window + * will be updated on the next WM_PAINT message. + */ + + while (num_chars-- > 0) { + switch (*buf) { + case '\r': + break; + case '\n': + if (nchars > 0) { + rc.left = GetXFromCurrentY(GetDC(hwnd),iHscrollPos,cur_x - nchars); + rc.right = rc.left + cxCharMax*nchars; + rc.top = cyChar * (cur_y-iVscrollPos); + rc.bottom = rc.top + cyChar; + InvalidateRect(hwnd, &rc, TRUE); + nchars = 0; + } + ConCarriageFeed(1); + ConScrollScreen(); + break; + case SET_CURSOR: + if (nchars > 0) { + rc.left = GetXFromCurrentY(GetDC(hwnd),iHscrollPos,cur_x - nchars); + rc.right = rc.left + cxCharMax*nchars; + rc.top = cyChar * (cur_y-iVscrollPos); + rc.bottom = rc.top + cyChar; + InvalidateRect(hwnd, &rc, TRUE); + nchars = 0; + } + buf++; + from = *((int *)buf); + buf += sizeof(int)/sizeof(TCHAR); + to = *((int *)buf); + buf += (sizeof(int)/sizeof(TCHAR))-1; + num_chars -= 2 * (sizeof(int)/sizeof(TCHAR)); + while (to > from) { + cur_x++; + if (GetXFromCurrentY(GetDC(hwnd),0,cur_x)+cxChar > + (LINE_LENGTH * cxChar)) { + cur_x = 0; + cur_y++; + ensure_line_below(); + cur_line = cur_line->next; + } + from++; + } + while (to < from) { + cur_x--; + if (cur_x < 0) { + cur_y--; + cur_line = cur_line->prev; + cur_x = cur_line->width-1; + } + from--; + } + + break; + default: + nchars++; + cur_line->text[cur_x] = *buf; + cur_x++; + if (cur_x > cur_line->width) + cur_line->width = cur_x; + if (GetXFromCurrentY(GetDC(hwnd),0,cur_x)+cxChar > + (LINE_LENGTH * cxChar)) { + if (nchars > 0) { + rc.left = GetXFromCurrentY(GetDC(hwnd),iHscrollPos,cur_x - nchars); + rc.right = rc.left + cxCharMax*nchars; + rc.top = cyChar * (cur_y-iVscrollPos); + rc.bottom = rc.top + cyChar; + InvalidateRect(hwnd, &rc, TRUE); + } + ConCarriageFeed(0); + nchars = 0; + } + } + buf++; + } + if (nchars > 0) { + rc.left = GetXFromCurrentY(GetDC(hwnd),iHscrollPos,cur_x - nchars); + rc.right = rc.left + cxCharMax*nchars; + rc.top = cyChar * (cur_y-iVscrollPos); + rc.bottom = rc.top + cyChar; + InvalidateRect(hwnd, &rc, TRUE); + } + ConScrollScreen(); + SetCaretPos(GetXFromCurrentY(GetDC(hwnd),iHscrollPos,cur_x), (cur_y-iVscrollPos)*cyChar); + outbuf.wrPos = outbuf.rdPos = 0; + ReleaseSemaphore(console_output, 1, NULL); +} + +static void +AddToCmdHistory(void) +{ + int i; + int size; + Uint32 *buf; + wchar_t cmdBuf[128]; + + if (llen != 0) { + for (i = 0, size = 0; i < llen-1; i++) { + /* + * Find end of prompt. + */ + if ((lbuf[i] == '>') && lbuf[i+1] == ' ') { + buf = &lbuf[i+2]; + size = llen-i-2; + break; + } + } + if (size > 0 && size < 128) { + for (i = 0;i < size; ++i) { + cmdBuf[i] = (wchar_t) buf[i]; + } + cmdBuf[size] = 0; + SendMessage(hComboWnd,CB_INSERTSTRING,0,(LPARAM)cmdBuf); + } + } +} + +static TBBUTTON tbb[] = +{ + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, + 0, IDMENU_COPY, TBSTATE_ENABLED, TBSTYLE_AUTOSIZE, 0, 0, 0, 0, + 1, IDMENU_PASTE, TBSTATE_ENABLED, TBSTYLE_AUTOSIZE, 0, 0, 0, 0, + 2, IDMENU_FONT, TBSTATE_ENABLED, TBSTYLE_AUTOSIZE, 0, 0, 0, 0, + 3, IDMENU_ABOUT, TBSTATE_ENABLED, TBSTYLE_AUTOSIZE, 0, 0, 0, 0, + 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, +}; + +static TBADDBITMAP tbbitmap = +{ + HINST_COMMCTRL, IDB_STD_SMALL_COLOR, +}; + + +static HWND +InitToolBar(HWND hwndParent) +{ + int x,y,cx; + HWND hwndTB,hwndTT; + RECT r; + TOOLINFO ti; + HFONT hFontNew; + DWORD backgroundColor = GetSysColor(COLOR_BTNFACE); + COLORMAP colorMap; + colorMap.from = RGB(192, 192, 192); + colorMap.to = backgroundColor; + + /* Create toolbar window with tooltips */ + hwndTB = CreateWindowEx(0,TOOLBARCLASSNAME,(TCHAR *)NULL, + WS_CHILD|CCS_TOP|WS_CLIPSIBLINGS|TBSTYLE_TOOLTIPS, + 0,0,0,0,hwndParent, + (HMENU)2,hInstance,NULL); + SendMessage(hwndTB,TB_BUTTONSTRUCTSIZE, + (WPARAM) sizeof(TBBUTTON),0); + tbbitmap.hInst = NULL; + tbbitmap.nID = (UINT) CreateMappedBitmap(beam_module, 1,0, &colorMap, 1); + SendMessage(hwndTB, TB_ADDBITMAP, (WPARAM) 4, + (WPARAM) &tbbitmap); + SendMessage(hwndTB,TB_ADDBUTTONS, (WPARAM) 30, + (LPARAM) (LPTBBUTTON) tbb); + if (toolbarVisible) + ShowWindow(hwndTB, SW_SHOW); + + /* Create combobox window */ + SendMessage(hwndTB,TB_GETITEMRECT,0,(LPARAM)&r); + x = r.left; y = r.top; + SendMessage(hwndTB,TB_GETITEMRECT,23,(LPARAM)&r); + cx = r.right - x + 1; + hComboWnd = CreateWindow(TEXT("combobox"),NULL,WS_VSCROLL|WS_CHILD|WS_VISIBLE|CBS_DROPDOWNLIST, + x,y,cx,100,hwndParent,(HMENU)ID_COMBOBOX, hInstance,NULL); + SetParent(hComboWnd,hwndTB); + hFontNew = CreateFontIndirect(&logfont); + SendMessage(hComboWnd,WM_SETFONT,(WPARAM)hFontNew, + MAKELPARAM(1,0)); + + /* Add tooltip for combo box */ + ZeroMemory(&ti,sizeof(TOOLINFO)); + ti.cbSize = sizeof(TOOLINFO); + ti.uFlags = TTF_IDISHWND|TTF_CENTERTIP|TTF_SUBCLASS; + ti.hwnd = hwndTB;; + ti.uId = (UINT)hComboWnd; + ti.lpszText = LPSTR_TEXTCALLBACK; + hwndTT = (HWND)SendMessage(hwndTB,TB_GETTOOLTIPS,0,0); + SendMessage(hwndTT,TTM_ADDTOOL,0,(LPARAM)&ti); + + return hwndTB; +} + +static void +window_title(struct title_buf *tbuf) +{ + int res, i; + size_t bufsz = TITLE_BUF_SZ; + unsigned char charbuff[TITLE_BUF_SZ]; + + res = erl_drv_getenv("ERL_WINDOW_TITLE", charbuff, &bufsz); + if (res < 0) + tbuf->name = erlang_window_title; + else if (res == 0) { + for (i = 0; i < bufsz; ++i) { + tbuf->buf[i] = charbuff[i]; + } + tbuf->buf[bufsz - 1] = 0; + tbuf->name = &tbuf->buf[0]; + } else { + char *buf = ALLOC(bufsz); + if (!buf) + tbuf->name = erlang_window_title; + else { + while (1) { + char *newbuf; + res = erl_drv_getenv("ERL_WINDOW_TITLE", buf, &bufsz); + if (res <= 0) { + if (res == 0) { + TCHAR *wbuf = ALLOC(bufsz *sizeof(TCHAR)); + for (i = 0; i < bufsz ; ++i) { + wbuf[i] = buf[i]; + } + wbuf[bufsz - 1] = 0; + FREE(buf); + tbuf->name = wbuf; + } else { + tbuf->name = erlang_window_title; + FREE(buf); + } + break; + } + newbuf = REALLOC(buf, bufsz); + if (newbuf) + buf = newbuf; + else { + tbuf->name = erlang_window_title; + FREE(buf); + break; + } + } + } + } +} + +static void +free_window_title(struct title_buf *tbuf) +{ + if (tbuf->name != erlang_window_title && tbuf->name != &tbuf->buf[0]) + FREE(tbuf->name); +} diff --git a/erts/emulator/drivers/win32/win_con.h b/erts/emulator/drivers/win32/win_con.h new file mode 100644 index 0000000000..d46af86ca5 --- /dev/null +++ b/erts/emulator/drivers/win32/win_con.h @@ -0,0 +1,39 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2007-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +/* + * External API for the windows console (aka werl window) + * used by ttsl_drv.c + */ +#ifndef _WIN_CON_H_VISITED +#define _WIN_CON_H_VISITED 1 +void ConNormalExit(void); +void ConWaitForExit(void); +void ConSetCtrlHandler(BOOL (WINAPI *handler)(DWORD)); +int ConPutChar(Uint32 c); +void ConSetCursor(int from, int to); +void ConPrintf(char *format, ...); +void ConVprintf(char *format, va_list va); +void ConBeep(void); +int ConReadInput(Uint32 *data, int nbytes); +int ConGetKey(void); +int ConGetColumns(void); +int ConGetRows(void); +void ConInit(void); +#endif /* _WIN_CON_H_VISITED */ diff --git a/erts/emulator/drivers/win32/win_efile.c b/erts/emulator/drivers/win32/win_efile.c new file mode 100644 index 0000000000..89aaad31da --- /dev/null +++ b/erts/emulator/drivers/win32/win_efile.c @@ -0,0 +1,1426 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1997-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +/* + * Purpose: Provides file and directory operations for Windows. + */ + +#include <windows.h> +#include "sys.h" +#include <ctype.h> + +#include "erl_efile.h" + +/* + * Microsoft-specific function to map a WIN32 error code to a Posix errno. + */ + +#define ISSLASH(a) ((a) == '\\' || (a) == '/') + +#define ISDIR(st) (((st).st_mode&S_IFMT) == S_IFDIR) +#define ISREG(st) (((st).st_mode&S_IFMT) == S_IFREG) + +#define IS_DOT_OR_DOTDOT(s) \ + (s[0] == '.' && (s[1] == '\0' || (s[1] == '.' && s[2] == '\0'))) + +#ifndef INVALID_FILE_ATTRIBUTES +#define INVALID_FILE_ATTRIBUTES ((DWORD) 0xFFFFFFFF) +#endif + +static int check_error(int result, Efile_error* errInfo); +static int set_error(Efile_error* errInfo); +static int IsRootUNCName(const char* path); +static int extract_root(char* name); +static unsigned short dos_to_posix_mode(int attr, const char *name); + +static int errno_map(DWORD last_error) { + + switch (last_error) { + case ERROR_SUCCESS: + return 0; + case ERROR_INVALID_FUNCTION: + case ERROR_INVALID_DATA: + case ERROR_INVALID_PARAMETER: + case ERROR_INVALID_TARGET_HANDLE: + case ERROR_INVALID_CATEGORY: + case ERROR_NEGATIVE_SEEK: + return EINVAL; + case ERROR_DIR_NOT_EMPTY: + return EEXIST; + case ERROR_BAD_FORMAT: + return ENOEXEC; + case ERROR_PATH_NOT_FOUND: + case ERROR_FILE_NOT_FOUND: + case ERROR_NO_MORE_FILES: + return ENOENT; + case ERROR_TOO_MANY_OPEN_FILES: + return EMFILE; + case ERROR_ACCESS_DENIED: + case ERROR_INVALID_ACCESS: + case ERROR_CURRENT_DIRECTORY: + case ERROR_SHARING_VIOLATION: + case ERROR_LOCK_VIOLATION: + case ERROR_INVALID_PASSWORD: + case ERROR_DRIVE_LOCKED: + return EACCES; + case ERROR_INVALID_HANDLE: + return EBADF; + case ERROR_NOT_ENOUGH_MEMORY: + case ERROR_OUTOFMEMORY: + case ERROR_OUT_OF_STRUCTURES: + return ENOMEM; + case ERROR_INVALID_DRIVE: + case ERROR_BAD_UNIT: + case ERROR_NOT_READY: + case ERROR_REM_NOT_LIST: + case ERROR_DUP_NAME: + case ERROR_BAD_NETPATH: + case ERROR_NETWORK_BUSY: + case ERROR_DEV_NOT_EXIST: + case ERROR_BAD_NET_NAME: + return ENXIO; + case ERROR_NOT_SAME_DEVICE: + return EXDEV; + case ERROR_WRITE_PROTECT: + return EROFS; + case ERROR_BAD_LENGTH: + case ERROR_BUFFER_OVERFLOW: + return E2BIG; + case ERROR_SEEK: + case ERROR_SECTOR_NOT_FOUND: + return ESPIPE; + case ERROR_NOT_DOS_DISK: + return ENODEV; + case ERROR_GEN_FAILURE: + return ENODEV; + case ERROR_SHARING_BUFFER_EXCEEDED: + case ERROR_NO_MORE_SEARCH_HANDLES: + return EMFILE; + case ERROR_HANDLE_EOF: + case ERROR_BROKEN_PIPE: + return EPIPE; + case ERROR_HANDLE_DISK_FULL: + case ERROR_DISK_FULL: + return ENOSPC; + case ERROR_NOT_SUPPORTED: + return ENOTSUP; + case ERROR_FILE_EXISTS: + case ERROR_ALREADY_EXISTS: + case ERROR_CANNOT_MAKE: + return EEXIST; + case ERROR_ALREADY_ASSIGNED: + return EBUSY; + case ERROR_NO_PROC_SLOTS: + return EAGAIN; + case ERROR_ARENA_TRASHED: + case ERROR_INVALID_BLOCK: + case ERROR_BAD_ENVIRONMENT: + case ERROR_BAD_COMMAND: + case ERROR_CRC: + case ERROR_OUT_OF_PAPER: + case ERROR_READ_FAULT: + case ERROR_WRITE_FAULT: + case ERROR_WRONG_DISK: + case ERROR_NET_WRITE_FAULT: + return EIO; + default: /* not to do with files I expect. */ + return EIO; + } +} + +static int +check_error(int result, Efile_error* errInfo) +{ + if (result < 0) { + errInfo->posix_errno = errno; + errInfo->os_errno = GetLastError(); + return 0; + } + return 1; +} + +/* + * Fills the provided error information structure with information + * with the error code given by GetLastError() and its corresponding + * Posix error number. + * + * Returns 0. + */ + +static int +set_error(Efile_error* errInfo) +{ + errInfo->posix_errno = errno_map(errInfo->os_errno = GetLastError()); + return 0; +} + +/* + * A writev with Unix semantics, but with Windows arguments + */ +static int +win_writev(Efile_error* errInfo, + HANDLE fd, /* handle to file */ + FILE_SEGMENT_ELEMENT iov[], /* array of buffer pointers */ + DWORD *size) /* number of bytes to write */ +{ + OVERLAPPED ov; + ov.Offset = 0L; + ov.OffsetHigh = 0L; + ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (ov.hEvent == NULL) + return set_error(errInfo); + if (! write_file_gather(fd, iov, *size, NULL, &ov)) + return set_error(errInfo); + if (WaitForSingleObject(ov.hEvent, INFINITE) != WAIT_OBJECT_0) + return set_error(errInfo); + if (! GetOverlappedResult(fd, &ov, size, FALSE)) + return set_error(errInfo); + return 1; +} + + + +int +efile_mkdir(errInfo, name) +Efile_error* errInfo; /* Where to return error codes. */ +char* name; /* Name of directory to create. */ +{ + return check_error(mkdir(name), errInfo); +} + +int +efile_rmdir(errInfo, name) +Efile_error* errInfo; /* Where to return error codes. */ +char* name; /* Name of directory to delete. */ +{ + OSVERSIONINFO os; + DWORD attr; + + if (RemoveDirectory(name) != FALSE) { + return 1; + } + errno = errno_map(GetLastError()); + if (errno == EACCES) { + attr = GetFileAttributes(name); + if (attr != (DWORD) -1) { + if ((attr & FILE_ATTRIBUTE_DIRECTORY) == 0) { + /* + * Windows 95 reports calling RemoveDirectory on a file as an + * EACCES, not an ENOTDIR. + */ + + errno = ENOTDIR; + goto end; + } + + /* + * Windows 95 reports removing a non-empty directory as + * an EACCES, not an EEXIST. If the directory is not empty, + * change errno so caller knows what's going on. + */ + + os.dwOSVersionInfoSize = sizeof(os); + GetVersionEx(&os); + if (os.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) { + HANDLE handle; + WIN32_FIND_DATA data; + char buffer[2*MAX_PATH]; + int len; + + len = strlen(name); + strcpy(buffer, name); + if (buffer[0] && buffer[len-1] != '\\' && buffer[len-1] != '/') { + strcat(buffer, "\\"); + } + strcat(buffer, "*.*"); + handle = FindFirstFile(buffer, &data); + if (handle != INVALID_HANDLE_VALUE) { + while (1) { + if ((strcmp(data.cFileName, ".") != 0) + && (strcmp(data.cFileName, "..") != 0)) { + /* + * Found something in this directory. + */ + + errno = EEXIST; + break; + } + if (FindNextFile(handle, &data) == FALSE) { + break; + } + } + FindClose(handle); + } + } + } + } + + if (errno == ENOTEMPTY) { + /* + * Posix allows both EEXIST or ENOTEMPTY, but we'll always + * return EEXIST to allow easy matching in Erlang code. + */ + + errno = EEXIST; + } + + end: + return check_error(-1, errInfo); +} + +int +efile_delete_file(errInfo, name) +Efile_error* errInfo; /* Where to return error codes. */ +char* name; /* Name of file to delete. */ +{ + DWORD attr; + + if (DeleteFile(name) != FALSE) { + return 1; + } + + errno = errno_map(GetLastError()); + if (errno == EACCES) { + attr = GetFileAttributes(name); + if (attr != (DWORD) -1) { + if (attr & FILE_ATTRIBUTE_DIRECTORY) { + /* + * Windows NT reports removing a directory as EACCES instead + * of EPERM. + */ + + errno = EPERM; + } + } + } else if (errno == ENOENT) { + attr = GetFileAttributes(name); + if (attr != (DWORD) -1) { + if (attr & FILE_ATTRIBUTE_DIRECTORY) { + /* + * Windows 95 reports removing a directory as ENOENT instead + * of EPERM. + */ + + errno = EPERM; + } + } + } else if (errno == EINVAL) { + /* + * Windows NT reports removing a char device as EINVAL instead of + * EACCES. + */ + + errno = EACCES; + } + + return check_error(-1, errInfo); +} + +/* + *--------------------------------------------------------------------------- + * + * Changes the name of an existing file or directory, from src to dst. + * If src and dst refer to the same file or directory, does nothing + * and returns success. Otherwise if dst already exists, it will be + * deleted and replaced by src subject to the following conditions: + * If src is a directory, dst may be an empty directory. + * If src is a file, dst may be a file. + * In any other situation where dst already exists, the rename will + * fail. + * + * Some possible error codes: + * + * EACCES: src or dst parent directory can't be read and/or written. + * EEXIST: dst is a non-empty directory. + * EINVAL: src is a root directory or dst is a subdirectory of src. + * EISDIR: dst is a directory, but src is not. + * ENOENT: src doesn't exist, or src or dst is "". + * ENOTDIR: src is a directory, but dst is not. + * EXDEV: src and dst are on different filesystems. + * + * Side effects: + * The implementation of rename may allow cross-filesystem renames, + * but the caller should be prepared to emulate it with copy and + * delete if errno is EXDEV. + * + *--------------------------------------------------------------------------- + */ + +int +efile_rename(errInfo, src, dst) +Efile_error* errInfo; /* Where to return error codes. */ +char* src; /* Original name. */ +char* dst; /* New name. */ +{ + DWORD srcAttr, dstAttr; + + if (MoveFile(src, dst) != FALSE) { + return 1; + } + + errno = errno_map(GetLastError()); + srcAttr = GetFileAttributes(src); + dstAttr = GetFileAttributes(dst); + if (srcAttr == (DWORD) -1) { + srcAttr = 0; + } + if (dstAttr == (DWORD) -1) { + dstAttr = 0; + } + + if (errno == EBADF) { + errno = EACCES; + return check_error(-1, errInfo); + } + if (errno == EACCES) { + decode: + if (srcAttr & FILE_ATTRIBUTE_DIRECTORY) { + char srcPath[MAX_PATH], dstPath[MAX_PATH]; + char *srcRest, *dstRest; + int size; + + size = GetFullPathName(src, sizeof(srcPath), srcPath, &srcRest); + if ((size == 0) || (size > sizeof(srcPath))) { + return check_error(-1, errInfo); + } + size = GetFullPathName(dst, sizeof(dstPath), dstPath, &dstRest); + if ((size == 0) || (size > sizeof(dstPath))) { + return check_error(-1, errInfo); + } + if (srcRest == NULL) { + srcRest = srcPath + strlen(srcPath); + } + if (strnicmp(srcPath, dstPath, srcRest - srcPath) == 0) { + /* + * Trying to move a directory into itself. + */ + + errno = EINVAL; + } + if (extract_root(srcPath)) { + /* + * Attempt to move a root directory. Never allowed. + */ + errno = EINVAL; + } + + (void) extract_root(dstPath); + if (dstPath[0] == '\0') { + /* + * The filename was invalid. (Don't know why, + * but play it safe.) + */ + errno = EINVAL; + } + if (stricmp(srcPath, dstPath) != 0) { + /* + * If src is a directory and dst filesystem != src + * filesystem, errno should be EXDEV. It is very + * important to get this behavior, so that the caller + * can respond to a cross filesystem rename by + * simulating it with copy and delete. The MoveFile + * system call already handles the case of moving a + * *file* between filesystems. + */ + + errno = EXDEV; + } + } + + /* + * Other types of access failure is that dst is a read-only + * filesystem, that an open file referred to src or dest, or that + * src or dest specified the current working directory on the + * current filesystem. EACCES is returned for those cases. + */ + + } else if (errno == EEXIST) { + /* + * Reports EEXIST any time the target already exists. If it makes + * sense, remove the old file and try renaming again. + */ + + if (srcAttr & FILE_ATTRIBUTE_DIRECTORY) { + if (dstAttr & FILE_ATTRIBUTE_DIRECTORY) { + /* + * Overwrite empty dst directory with src directory. The + * following call will remove an empty directory. If it + * fails, it's because it wasn't empty. + */ + + if (RemoveDirectory(dst)) { + /* + * Now that that empty directory is gone, we can try + * renaming again. If that fails, we'll put this empty + * directory back, for completeness. + */ + + if (MoveFile(src, dst) != FALSE) { + return 1; + } + + /* + * Some new error has occurred. Don't know what it + * could be, but report this one. + */ + + errno = errno_map(GetLastError()); + CreateDirectory(dst, NULL); + SetFileAttributes(dst, dstAttr); + if (errno == EACCES) { + /* + * Decode the EACCES to a more meaningful error. + */ + + goto decode; + } + } + } else { /* (dstAttr & FILE_ATTRIBUTE_DIRECTORY) == 0 */ + errno = ENOTDIR; + } + } else { /* (srcAttr & FILE_ATTRIBUTE_DIRECTORY) == 0 */ + if (dstAttr & FILE_ATTRIBUTE_DIRECTORY) { + errno = EISDIR; + } else { + /* + * Overwrite existing file by: + * + * 1. Rename existing file to temp name. + * 2. Rename old file to new name. + * 3. If success, delete temp file. If failure, + * put temp file back to old name. + */ + + char tempName[MAX_PATH]; + int result, size; + char *rest; + + size = GetFullPathName(dst, sizeof(tempName), tempName, &rest); + if ((size == 0) || (size > sizeof(tempName)) || (rest == NULL)) { + return check_error(-1, errInfo); + } + *rest = '\0'; + result = -1; + if (GetTempFileName(tempName, "erlr", 0, tempName) != 0) { + /* + * Strictly speaking, need the following DeleteFile and + * MoveFile to be joined as an atomic operation so no + * other app comes along in the meantime and creates the + * same temp file. + */ + + DeleteFile(tempName); + if (MoveFile(dst, tempName) != FALSE) { + if (MoveFile(src, dst) != FALSE) { + SetFileAttributes(tempName, FILE_ATTRIBUTE_NORMAL); + DeleteFile(tempName); + return 1; + } else { + DeleteFile(dst); + MoveFile(tempName, dst); + } + } + + /* + * Can't backup dst file or move src file. Return that + * error. Could happen if an open file refers to dst. + */ + + errno = errno_map(GetLastError()); + if (errno == EACCES) { + /* + * Decode the EACCES to a more meaningful error. + */ + + goto decode; + } + } + return result; + } + } + } + return check_error(-1, errInfo); +} + +int +efile_chdir(errInfo, name) +Efile_error* errInfo; /* Where to return error codes. */ +char* name; /* Name of directory to make current. */ +{ + int success = check_error(chdir(name), errInfo); + if (!success && errInfo->posix_errno == EINVAL) + /* POSIXification of errno */ + errInfo->posix_errno = ENOENT; + return success; +} + +int +efile_getdcwd(errInfo, drive, buffer, size) +Efile_error* errInfo; /* Where to return error codes. */ +int drive; /* 0 - current, 1 - A, 2 - B etc. */ +char* buffer; /* Where to return the current directory. */ +size_t size; /* Size of buffer. */ +{ + if (_getdcwd(drive, buffer, size) == NULL) + return check_error(-1, errInfo); + for ( ; *buffer; buffer++) + if (*buffer == '\\') + *buffer = '/'; + return 1; +} + +int +efile_readdir(errInfo, name, dir_handle, buffer, size) +Efile_error* errInfo; /* Where to return error codes. */ +char* name; /* Name of directory to open. */ +EFILE_DIR_HANDLE* dir_handle; /* Directory handle of open directory. */ +char* buffer; /* Pointer to buffer for one filename. */ +size_t size; /* Size of buffer. */ +{ + HANDLE dir; /* Handle to directory. */ + char wildcard[MAX_PATH]; /* Wildcard to search for. */ + WIN32_FIND_DATA findData; /* Data found by FindFirstFile() or FindNext(). */ + + /* + * First time we must setup everything. + */ + + if (*dir_handle == NULL) { + int length = strlen(name); + char* s; + + if (length+3 >= MAX_PATH) { + errno = ENAMETOOLONG; + return check_error(-1, errInfo); + } + + strcpy(wildcard, name); + s = wildcard+length-1; + if (*s != '/' && *s != '\\') + *++s = '\\'; + *++s = '*'; + *++s = '\0'; + DEBUGF(("Reading %s\n", wildcard)); + dir = FindFirstFile(wildcard, &findData); + if (dir == INVALID_HANDLE_VALUE) + return set_error(errInfo); + *dir_handle = (EFILE_DIR_HANDLE) dir; + + if (!IS_DOT_OR_DOTDOT(findData.cFileName)) { + strcpy(buffer, findData.cFileName); + return 1; + } + } + + + /* + * Retrieve the name of the next file using the directory handle. + */ + + dir = (HANDLE) *dir_handle; + + for (;;) { + if (FindNextFile(dir, &findData)) { + if (IS_DOT_OR_DOTDOT(findData.cFileName)) + continue; + strcpy(buffer, findData.cFileName); + return 1; + } + + if (GetLastError() == ERROR_NO_MORE_FILES) { + FindClose(dir); + errInfo->posix_errno = errInfo->os_errno = 0; + return 0; + } + + set_error(errInfo); + FindClose(dir); + return 0; + } +} + +int +efile_openfile(errInfo, name, flags, pfd, pSize) +Efile_error* errInfo; /* Where to return error codes. */ +char* name; /* Name of directory to open. */ +int flags; /* Flags to use for opening. */ +int* pfd; /* Where to store the file descriptor. */ +Sint64* pSize; /* Where to store the size of the file. */ +{ + BY_HANDLE_FILE_INFORMATION fileInfo; /* File information from a handle. */ + HANDLE fd; /* Handle to open file. */ + DWORD access; /* Access mode: GENERIC_READ, GENERIC_WRITE. */ + DWORD crFlags; + + switch (flags & (EFILE_MODE_READ|EFILE_MODE_WRITE)) { + case EFILE_MODE_READ: + access = GENERIC_READ; + crFlags = OPEN_EXISTING; + break; + case EFILE_MODE_WRITE: + access = GENERIC_WRITE; + crFlags = CREATE_ALWAYS; + break; + case EFILE_MODE_READ_WRITE: + access = GENERIC_READ|GENERIC_WRITE; + crFlags = OPEN_ALWAYS; + break; + default: + errno = EINVAL; + check_error(-1, errInfo); + return 0; + } + + if (flags & EFILE_MODE_APPEND) { + crFlags = OPEN_ALWAYS; + } + fd = CreateFile(name, access, FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, crFlags, FILE_ATTRIBUTE_NORMAL, NULL); + + /* + * Check for errors. + */ + + if (fd == INVALID_HANDLE_VALUE) { + DWORD attr; + + set_error(errInfo); + + /* + * If the error is EACESS, the reason could be that we tried to + * open a directory. In that case, we'll change the error code + * to EISDIR. + */ + if (errInfo->posix_errno && + (attr = GetFileAttributes(name)) != INVALID_FILE_ATTRIBUTES && + (attr & FILE_ATTRIBUTE_DIRECTORY)) { + errInfo->posix_errno = EISDIR; + } + return 0; + } + + /* + * Get and return the length of the open file. + */ + + if (!GetFileInformationByHandle(fd, &fileInfo)) + return set_error(errInfo); + *pfd = (int) fd; + if (pSize) { + *pSize = (Sint64) + (((Uint64)fileInfo.nFileSizeHigh << 32) | + (Uint64)fileInfo.nFileSizeLow); + } + return 1; +} + +int +efile_may_openfile(Efile_error* errInfo, char *name) { + DWORD attr; + + if ((attr = GetFileAttributes(name)) == INVALID_FILE_ATTRIBUTES) { + return check_error(-1, errInfo); + } + + if (attr & FILE_ATTRIBUTE_DIRECTORY) { + errno = EISDIR; + return check_error(-1, errInfo); + } + return 1; +#if 0 + struct stat statbuf; + + if (stat(name, &statbuf)) { + return check_error(-1, errInfo); + } + if (ISDIR(statbuf)) { + errno = EISDIR; + return check_error(-1, errInfo); + } + return 1; +#endif +} + +void +efile_closefile(fd) +int fd; /* File descriptor for file to close. */ +{ + CloseHandle((HANDLE) fd); +} + +int +efile_fsync(errInfo, fd) +Efile_error* errInfo; /* Where to return error codes. */ +int fd; /* File descriptor for file to sync. */ +{ + if (!FlushFileBuffers((HANDLE) fd)) { + return check_error(-1, errInfo); + } + return 1; +} + +int +efile_fileinfo(Efile_error* errInfo, Efile_info* pInfo, + char* orig_name, int info_for_link) +{ + HANDLE findhandle; /* Handle returned by FindFirstFile(). */ + WIN32_FIND_DATA findbuf; /* Data return by FindFirstFile(). */ + char name[_MAX_PATH]; + int name_len; + char* path; + char pathbuf[_MAX_PATH]; + int drive; /* Drive for filename (1 = A:, 2 = B: etc). */ + + /* Don't allow wildcards to be interpreted by system */ + + if (strpbrk(orig_name, "?*")) { + enoent: + errInfo->posix_errno = ENOENT; + errInfo->os_errno = ERROR_FILE_NOT_FOUND; + return 0; + } + + /* + * Move the name to a buffer and make sure to remove a trailing + * slash, because it causes FindFirstFile() to fail on Win95. + */ + + if ((name_len = strlen(orig_name)) >= _MAX_PATH) { + goto enoent; + } else { + strcpy(name, orig_name); + if (name_len > 2 && ISSLASH(name[name_len-1]) && + name[name_len-2] != ':') { + name[name_len-1] = '\0'; + } + } + + /* Try to get disk from name. If none, get current disk. */ + + if (name[1] != ':') { + drive = 0; + if (GetCurrentDirectory(sizeof(pathbuf), pathbuf) && + pathbuf[1] == ':') { + drive = tolower(pathbuf[0]) - 'a' + 1; + } + } else if (*name && name[2] == '\0') { + /* + * X: and nothing more is an error. + */ + errInfo->posix_errno = ENOENT; + errInfo->os_errno = ERROR_FILE_NOT_FOUND; + return 0; + } else + drive = tolower(*name) - 'a' + 1; + + findhandle = FindFirstFile(name, &findbuf); + if (findhandle == INVALID_HANDLE_VALUE) { + if (!(strpbrk(name, "./\\") && + (path = _fullpath(pathbuf, name, _MAX_PATH)) && + /* root dir. ('C:\') or UNC root dir. ('\\server\share\') */ + ((strlen(path) == 3) || IsRootUNCName(path)) && + (GetDriveType(path) > 1) ) ) { + errInfo->posix_errno = ENOENT; + errInfo->os_errno = ERROR_FILE_NOT_FOUND; + return 0; + } + + /* + * Root directories (such as C:\ or \\server\share\ are fabricated. + */ + + findbuf.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY; + findbuf.nFileSizeHigh = 0; + findbuf.nFileSizeLow = 0; + findbuf.cFileName[0] = '\0'; + + pInfo->modifyTime.year = 1980; + pInfo->modifyTime.month = 1; + pInfo->modifyTime.day = 1; + pInfo->modifyTime.hour = 0; + pInfo->modifyTime.minute = 0; + pInfo->modifyTime.second = 0; + + pInfo->accessTime = pInfo->modifyTime; + } else { + SYSTEMTIME SystemTime; + FILETIME LocalFTime; + +#define GET_TIME(dst, src) \ +if (!FileTimeToLocalFileTime(&findbuf.src, &LocalFTime) || \ + !FileTimeToSystemTime(&LocalFTime, &SystemTime)) { \ + return set_error(errInfo); \ +} \ +(dst).year = SystemTime.wYear; \ +(dst).month = SystemTime.wMonth; \ +(dst).day = SystemTime.wDay; \ +(dst).hour = SystemTime.wHour; \ +(dst).minute = SystemTime.wMinute; \ +(dst).second = SystemTime.wSecond; + + GET_TIME(pInfo->modifyTime, ftLastWriteTime); + + if (findbuf.ftLastAccessTime.dwLowDateTime == 0 && + findbuf.ftLastAccessTime.dwHighDateTime == 0) { + pInfo->accessTime = pInfo->modifyTime; + } else { + GET_TIME(pInfo->accessTime, ftLastAccessTime); + } + + if (findbuf.ftCreationTime.dwLowDateTime == 0 && + findbuf.ftCreationTime.dwHighDateTime == 0) { + pInfo->cTime = pInfo->modifyTime; + } else { + GET_TIME(pInfo->cTime, ftCreationTime); + } +#undef GET_TIME + FindClose(findhandle); + } + + pInfo->size_low = findbuf.nFileSizeLow; + pInfo->size_high = findbuf.nFileSizeHigh; + + if (findbuf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + pInfo->type = FT_DIRECTORY; + else + pInfo->type = FT_REGULAR; + + if (findbuf.dwFileAttributes & FILE_ATTRIBUTE_READONLY) + pInfo->access = FA_READ; + else + pInfo->access = FA_READ|FA_WRITE; + + pInfo->mode = dos_to_posix_mode(findbuf.dwFileAttributes, name); + pInfo->links = 1; + pInfo->major_device = drive; + pInfo->minor_device = 0; + pInfo->inode = 0; + pInfo->uid = 0; + pInfo->gid = 0; + + return 1; +} + +int +efile_write_info(errInfo, pInfo, name) +Efile_error* errInfo; +Efile_info* pInfo; +char* name; +{ + SYSTEMTIME timebuf; + FILETIME LocalFileTime; + FILETIME ModifyFileTime; + FILETIME AccessFileTime; + FILETIME CreationFileTime; + HANDLE fd; + FILETIME* mtime = NULL; + FILETIME* atime = NULL; + FILETIME* ctime = NULL; + DWORD attr; + DWORD tempAttr; + BOOL modifyTime = FALSE; + + /* + * Get the attributes for the file. + */ + + tempAttr = attr = GetFileAttributes((LPTSTR)name); + if (attr == 0xffffffff) { + return set_error(errInfo); + } + if (pInfo->mode != -1) { + if (pInfo->mode & _S_IWRITE) { + /* clear read only bit */ + attr &= ~FILE_ATTRIBUTE_READONLY; + } else { + /* set read only bit */ + attr |= FILE_ATTRIBUTE_READONLY; + } + } + + /* + * Construct all file times. + */ + +#define MKTIME(tb, ts, ptr) \ + timebuf.wYear = ts.year; \ + timebuf.wMonth = ts.month; \ + timebuf.wDay = ts.day; \ + timebuf.wHour = ts.hour; \ + timebuf.wMinute = ts.minute; \ + timebuf.wSecond = ts.second; \ + timebuf.wMilliseconds = 0; \ + if (ts.year != -1) { \ + modifyTime = TRUE; \ + ptr = &tb; \ + if (!SystemTimeToFileTime(&timebuf, &LocalFileTime ) || \ + !LocalFileTimeToFileTime(&LocalFileTime, &tb)) { \ + errno = EINVAL; \ + return check_error(-1, errInfo); \ + } \ + } + + MKTIME(ModifyFileTime, pInfo->accessTime, mtime); + MKTIME(AccessFileTime, pInfo->modifyTime, atime); + MKTIME(CreationFileTime, pInfo->cTime, ctime); +#undef MKTIME + + /* + * If necessary, set the file times. + */ + + if (modifyTime) { + /* + * If the has read only access, we must temporarily turn on + * write access (this is necessary for native filesystems, + * but not for NFS filesystems). + */ + + if (tempAttr & FILE_ATTRIBUTE_READONLY) { + tempAttr &= ~FILE_ATTRIBUTE_READONLY; + if (!SetFileAttributes((LPTSTR) name, tempAttr)) { + return set_error(errInfo); + } + } + + fd = CreateFile(name, GENERIC_READ|GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (fd != INVALID_HANDLE_VALUE) { + BOOL result = SetFileTime(fd, ctime, atime, mtime); + if (!result) { + return set_error(errInfo); + } + CloseHandle(fd); + } + } + + /* + * If the file doesn't have the correct attributes, set them now. + * (It could have been done before setting the file times, above). + */ + + if (tempAttr != attr) { + if (!SetFileAttributes((LPTSTR) name, attr)) { + return set_error(errInfo); + } + } + return 1; +} + + +int +efile_pwrite(errInfo, fd, buf, count, offset) +Efile_error* errInfo; /* Where to return error codes. */ +int fd; /* File descriptor to write to. */ +char* buf; /* Buffer to write. */ +size_t count; /* Number of bytes to write. */ +Sint64 offset; /* where to write it */ +{ + int res = efile_seek(errInfo, fd, offset, EFILE_SEEK_SET, NULL); + if (res) { + return efile_write(errInfo, EFILE_MODE_WRITE, fd, buf, count); + } else { + return res; + } +} + +/* position and read/write as a single atomic op */ +int +efile_pread(errInfo, fd, offset, buf, count, pBytesRead) +Efile_error* errInfo; /* Where to return error codes. */ +int fd; /* File descriptor to read from. */ +Sint64 offset; /* Offset in bytes from BOF. */ +char* buf; /* Buffer to read into. */ +size_t count; /* Number of bytes to read. */ +size_t* pBytesRead; /* Where to return number of bytes read. */ +{ + int res = efile_seek(errInfo, fd, offset, EFILE_SEEK_SET, NULL); + if (res) { + return efile_read(errInfo, EFILE_MODE_READ, fd, buf, count, pBytesRead); + } else { + return res; + } +} + + + +int +efile_write(errInfo, flags, fd, buf, count) +Efile_error* errInfo; /* Where to return error codes. */ +int flags; /* Flags given when file was opened. */ +int fd; /* File descriptor to write to. */ +char* buf; /* Buffer to write. */ +size_t count; /* Number of bytes to write. */ +{ + DWORD written; /* Bytes written in last operation. */ + + if (flags & EFILE_MODE_APPEND) { + (void) SetFilePointer((HANDLE) fd, 0, NULL, FILE_END); + } + while (count > 0) { + if (!WriteFile((HANDLE) fd, buf, count, &written, NULL)) + return set_error(errInfo); + buf += written; + count -= written; + } + return 1; +} + +int +efile_writev(Efile_error* errInfo, /* Where to return error codes */ + int flags, /* Flags given when file was + * opened */ + int fd, /* File descriptor to write to */ + SysIOVec* iov, /* Vector of buffer structs. + * The structs are unchanged + * after the call */ + int iovcnt, /* Number of structs in vector */ + size_t size) /* Number of bytes to write */ +{ + int cnt; /* Buffers so far written */ + + ASSERT(iovcnt >= 0); + + if (flags & EFILE_MODE_APPEND) { + (void) SetFilePointer((HANDLE) fd, 0, NULL, FILE_END); + } + for (cnt = 0; cnt < iovcnt; cnt++) { + if (iov[cnt].iov_base && iov[cnt].iov_len > 0) { + /* Non-empty buffer */ + int p; /* Position in buffer */ + int w = iov[cnt].iov_len;/* Bytes written in this call */ + for (p = 0; p < iov[cnt].iov_len; p += w) { + if (!WriteFile((HANDLE) fd, + iov[cnt].iov_base + p, + iov[cnt].iov_len - p, + &w, + NULL)) + return set_error(errInfo); + } + } + } + return 1; +} + +int +efile_read(errInfo, flags, fd, buf, count, pBytesRead) +Efile_error* errInfo; /* Where to return error codes. */ +int flags; /* Flags given when file was opened. */ +int fd; /* File descriptor to read from. */ +char* buf; /* Buffer to read into. */ +size_t count; /* Number of bytes to read. */ +size_t* pBytesRead; /* Where to return number of bytes read. */ +{ + if (!ReadFile((HANDLE) fd, buf, count, (DWORD *) pBytesRead, NULL)) + return set_error(errInfo); + return 1; +} + +int +efile_seek(errInfo, fd, offset, origin, new_location) +Efile_error* errInfo; /* Where to return error codes. */ +int fd; /* File descriptor to do the seek on. */ +Sint64 offset; /* Offset in bytes from the given origin. */ +int origin; /* Origin of seek (SEEK_SET, SEEK_CUR, + * SEEK_END). + */ +Sint64* new_location; /* Resulting new location in file. */ +{ + LARGE_INTEGER off, new_loc; + + switch (origin) { + case EFILE_SEEK_SET: origin = FILE_BEGIN; break; + case EFILE_SEEK_CUR: origin = FILE_CURRENT; break; + case EFILE_SEEK_END: origin = FILE_END; break; + default: + errno = EINVAL; + check_error(-1, errInfo); + break; + } + + off.QuadPart = offset; + if (! SetFilePointerEx((HANDLE) fd, off, + new_location ? &new_loc : NULL, origin)) { + return set_error(errInfo); + } + if (new_location) { + *new_location = new_loc.QuadPart; + DEBUGF(("efile_seek(offset=%ld, origin=%d) -> %ld\n", + (long) offset, origin, (long) *new_location)); + } else { + DEBUGF(("efile_seek(offset=%ld, origin=%d)\n", (long) offset, origin)); + } + return 1; +} + +int +efile_truncate_file(errInfo, fd, flags) +Efile_error* errInfo; /* Where to return error codes. */ +int *fd; /* File descriptor for file to truncate. */ +int flags; +{ + if (!SetEndOfFile((HANDLE) (*fd))) + return set_error(errInfo); + return 1; +} + + +/* + * IsRootUNCName - returns TRUE if the argument is a UNC name specifying + * a root share. That is, if it is of the form \\server\share\. + * This routine will also return true if the argument is of the + * form \\server\share (no trailing slash) but Win32 currently + * does not like that form. + * + * Forward slashes ('/') may be used instead of backslashes ('\'). + */ + +static int +IsRootUNCName(const char* path) +{ + /* + * If a root UNC name, path will start with 2 (but not 3) slashes + */ + + if ((strlen(path) >= 5) /* minimum string is "//x/y" */ + && ISSLASH(path[0]) && ISSLASH(path[1])) + { + const char * p = path + 2 ; + + /* + * find the slash between the server name and share name + */ + while ( * ++ p ) + if ( ISSLASH(*p) ) + break ; + + if ( *p && p[1] ) + { + /* + * is there a further slash? + */ + while ( * ++ p ) + if ( ISSLASH(*p) ) + break ; + + /* + * just final slash (or no final slash) + */ + if ( !*p || !p[1]) + return 1; + } + } + + return 0 ; +} + +/* + * Extracts the root part of an absolute filename (by modifying the string + * pointed to by the name argument). The name can start + * with either a driver letter (for example, C:\), or a UNC name + * (for example, \\guinness\bjorn). + * + * If the name is invalid, the buffer will be modified to point to + * an empty string. + * + * Returns: 1 if the name consists of just the root part, 0 if + * the name was longer. + */ + +static int +extract_root(char* name) +{ + int len = strlen(name); + + if (isalpha(name[0]) && name[1] == ':' && ISSLASH(name[2])) { + int c = name[3]; + name[3] = '\0'; + return c == '\0'; + } else if (len < 5 || !ISSLASH(name[0]) || !ISSLASH(name[1])) { + goto error; + } else { /* Try to find the end of the UNC name. */ + char* p; + int c; + + /* + * Find the slash between the server name and share name. + */ + + for (p = name + 2; *p; p++) + if (ISSLASH(*p)) + break; + if (*p == '\0') + goto error; + + /* + * Find the slash after the share name. + */ + + for (p++; *p; p++) + if (ISSLASH(*p)) + break; + c = *p; + *p = '\0'; + return c == '\0' || p[1] == '\0'; + } + + error: + *name = '\0'; + return 1; +} + +static unsigned short +dos_to_posix_mode(int attr, const char *name) +{ + register unsigned short uxmode; + unsigned dosmode; + register const char *p; + + dosmode = attr & 0xff; + if ((p = name)[1] == ':') + p += 2; + + /* check to see if this is a directory - note we must make a special + * check for the root, which DOS thinks is not a directory + */ + + uxmode = (unsigned short) + (((ISSLASH(*p) && !p[1]) || (dosmode & FILE_ATTRIBUTE_DIRECTORY) || + *p == '\0') ? _S_IFDIR|_S_IEXEC : _S_IFREG); + + /* If attribute byte does not have read-only bit, it is read-write */ + + uxmode |= (dosmode & FILE_ATTRIBUTE_READONLY) ? + _S_IREAD : (_S_IREAD|_S_IWRITE); + + /* see if file appears to be executable - check extension of name */ + + if (p = strrchr(name, '.')) { + if (!stricmp(p, ".exe") || + !stricmp(p, ".cmd") || + !stricmp(p, ".bat") || + !stricmp(p, ".com")) + uxmode |= _S_IEXEC; + } + + /* propagate user read/write/execute bits to group/other fields */ + + uxmode |= (uxmode & 0700) >> 3; + uxmode |= (uxmode & 0700) >> 6; + + return uxmode; +} + +int +efile_readlink(Efile_error* errInfo, char* name, char* buffer, size_t size) +{ + errno = ENOTSUP; + return check_error(-1, errInfo); +} + + +int +efile_altname(Efile_error* errInfo, char* orig_name, char* buffer, size_t size) +{ + WIN32_FIND_DATA wfd; + HANDLE fh; + char name[_MAX_PATH]; + int name_len; + char* path; + char pathbuf[_MAX_PATH]; + int drive; /* Drive for filename (1 = A:, 2 = B: etc). */ + + /* Don't allow wildcards to be interpreted by system */ + + if (strpbrk(orig_name, "?*")) { + enoent: + errInfo->posix_errno = ENOENT; + errInfo->os_errno = ERROR_FILE_NOT_FOUND; + return 0; + } + + /* + * Move the name to a buffer and make sure to remove a trailing + * slash, because it causes FindFirstFile() to fail on Win95. + */ + + if ((name_len = strlen(orig_name)) >= _MAX_PATH) { + goto enoent; + } else { + strcpy(name, orig_name); + if (name_len > 2 && ISSLASH(name[name_len-1]) && + name[name_len-2] != ':') { + name[name_len-1] = '\0'; + } + } + + /* Try to get disk from name. If none, get current disk. */ + + if (name[1] != ':') { + drive = 0; + if (GetCurrentDirectory(sizeof(pathbuf), pathbuf) && + pathbuf[1] == ':') { + drive = tolower(pathbuf[0]) - 'a' + 1; + } + } else if (*name && name[2] == '\0') { + /* + * X: and nothing more is an error. + */ + goto enoent; + } else { + drive = tolower(*name) - 'a' + 1; + } + fh = FindFirstFile(name,&wfd); + if (fh == INVALID_HANDLE_VALUE) { + if (!(strpbrk(name, "./\\") && + (path = _fullpath(pathbuf, name, _MAX_PATH)) && + /* root dir. ('C:\') or UNC root dir. ('\\server\share\') */ + ((strlen(path) == 3) || IsRootUNCName(path)) && + (GetDriveType(path) > 1) ) ) { + errno = errno_map(GetLastError()); + return check_error(-1, errInfo); + } + /* + * Root directories (such as C:\ or \\server\share\ are fabricated. + */ + strcpy(buffer,name); + return 1; + } + + strcpy(buffer,wfd.cAlternateFileName); + if (!*buffer) { + strcpy(buffer,wfd.cFileName); + } + + return 1; +} + +int +efile_link(Efile_error* errInfo, char* old, char* new) +{ + errno = ENOTSUP; + return check_error(-1, errInfo); +} + +int +efile_symlink(Efile_error* errInfo, char* old, char* new) +{ + errno = ENOTSUP; + return check_error(-1, errInfo); +} diff --git a/erts/emulator/drivers/win32/winsock_func.h b/erts/emulator/drivers/win32/winsock_func.h new file mode 100644 index 0000000000..9d2c099c4d --- /dev/null +++ b/erts/emulator/drivers/win32/winsock_func.h @@ -0,0 +1,102 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1997-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +typedef struct _WinSockFuncs { + int (WSAAPI *WSAStartup)(WORD wVersionRequired, LPWSADATA lpWSAData); + int (WSAAPI *WSACleanup)(void); + int (WSAAPI *WSAGetLastError)(void); + DWORD (WSAAPI *WSAWaitForMultipleEvents) (DWORD cEvents, + const WSAEVENT FAR * lphEvents, + BOOL fWaitAll, + DWORD dwTimeout, + BOOL fAlertable); + WSAEVENT (WSAAPI *WSACreateEvent)(void); + BOOL (WSAAPI *WSACloseEvent)(WSAEVENT hEvent); + + BOOL (WSAAPI *WSASetEvent)(WSAEVENT hEvent); + BOOL (WSAAPI *WSAResetEvent)(WSAEVENT hEvent); + int (WSAAPI *WSAEventSelect)(SOCKET s, WSAEVENT hEventObject, + long lNetworkEvents); + int (WSAAPI *WSAEnumNetworkEvents)(SOCKET s, + WSAEVENT hEventObject, + LPWSANETWORKEVENTS lpNetworkEvents); + int (WSAAPI *WSAIoctl)(SOCKET s, + DWORD dwIoControlCode, + LPVOID lpvInBuffer, + DWORD cbInBuffer, + LPVOID lpvOUTBuffer, + DWORD cbOUTBuffer, + LPDWORD lpcbBytesReturned, + LPWSAOVERLAPPED lpOverlapped, + LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE + ); + SOCKET (WSAAPI *accept)(SOCKET s, struct sockaddr FAR *addr, + int FAR *addrlen); + int (WSAAPI *bind)(SOCKET s, const struct sockaddr FAR *addr, + int namelen); + int (WSAAPI *closesocket)(SOCKET s); + int (WSAAPI *connect)(SOCKET s, const struct sockaddr FAR *name, + int namelen); + int (WSAAPI *ioctlsocket)(SOCKET s, long cmd, u_long FAR *argp); + int (WSAAPI *getsockopt)(SOCKET s, int level, int optname, + char FAR * optval, int FAR *optlen); + u_long (WSAAPI *htonl)(u_long hostlong); + u_short (WSAAPI *htons)(u_short hostshort); + unsigned long (WSAAPI *inet_addr)(const char FAR * cp); + char FAR * (WSAAPI *inet_ntoa)(struct in_addr in); + int (WSAAPI *listen)(SOCKET s, int backlog); + u_short (WSAAPI *ntohs)(u_short netshort); + int (WSAAPI *recv)(SOCKET s, char FAR * buf, int len, int flags); + int (WSAAPI *send)(SOCKET s, const char FAR * buf, int len, int flags); + int (WSAAPI *setsockopt)(SOCKET s, int level, int optname, + const char FAR * optval, int optlen); + int (WSAAPI *shutdown)(SOCKET s, int how); + SOCKET (WSAAPI *socket)(int af, int type, int protocol); + struct hostent FAR * (WSAAPI *gethostbyname)(const char FAR * name); + struct hostent FAR * (WSAAPI *gethostbyaddr)(const char FAR *addr, + int addrlen, int addrtype); + int (WSAAPI *gethostname)(char FAR * name, int namelen); + struct servent FAR * (WSAAPI *getservbyname)(const char FAR * name, + const char FAR * proto); + struct servent FAR * (WSAAPI *getservbyport)(int port, + const char FAR * proto); + int (WSAAPI *getsockname)(SOCKET sock, struct sockaddr FAR *name, + int FAR *namelen); + + /* + * New, added for inet_drv. + */ + + int (WSAAPI *getpeername)(SOCKET s, struct sockaddr FAR * name, + int FAR * namelen); + u_long (WSAAPI *ntohl)(u_long netlong); + int (WSAAPI *WSASend)(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, + LPDWORD lpNumberOfBytesSent, DWORD dwFlags, + LPWSAOVERLAPPED lpOverlapped, + LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); + int (WSAAPI *sendto)(SOCKET s, const char FAR * buf, int len, + int flags, const struct sockaddr FAR * to, int tolen); + int (WSAAPI *recvfrom)(SOCKET s, char FAR * buf, int len, int flags, + struct sockaddr FAR * from, int FAR * fromlen); +} WinSockFuncs; + + +extern WinSockFuncs winSock; + +extern int tcp_lookup_functions(void); |