diff --git a/src/client/cl_console.c b/src/client/cl_console.c index 827ad6db..23bf2115 100644 --- a/src/client/cl_console.c +++ b/src/client/cl_console.c @@ -60,8 +60,8 @@ DrawAltStringScaled(int x, int y, char *s, float factor) void Key_ClearTyping(void) { - key_lines[edit_line][1] = 0; /* clear any typing */ - key_linepos = 1; + key_lines[edit_line][0] = '\0'; + key_linepos = 0; } void @@ -471,6 +471,10 @@ Con_DrawInput(void) int i; float scale; char *text; + char ch; + int txtlen; + int linepos; + int draw_icon; if (cls.key_dest == key_menu) { @@ -485,29 +489,50 @@ Con_DrawInput(void) scale = SCR_GetConsoleScale(); text = key_lines[edit_line]; - - /* add the cursor frame */ - text[key_linepos] = 10 + ((int)(cls.realtime >> 8) & 1); - - /* fill out remainder with spaces */ - for (i = key_linepos + 1; i < con.linewidth; i++) - { - text[i] = ' '; - } + linepos = key_linepos; /* prestep if horizontally scrolling */ - if (key_linepos >= con.linewidth) + if (linepos >= (con.linewidth - 1)) { - text += 1 + key_linepos - con.linewidth; + int ofs = 1 + linepos - con.linewidth; + + text += ofs; + linepos -= ofs; + + draw_icon = 0; + } + else + { + Draw_CharScaled(8 * scale, con.vislines - 22 * scale, CON_INPUT_INDICATOR, scale); + draw_icon = 1; } - for (i = 0; i < con.linewidth; i++) - { - Draw_CharScaled(((i + 1) << 3) * scale, con.vislines - 22 * scale, text[i], scale); - } + txtlen = strlen(text); - /* remove cursor */ - key_lines[edit_line][key_linepos] = 0; + for (i = 0; i < (con.linewidth - draw_icon); i++) + { + if (i == linepos) + { + if (cls.realtime & 8) + { + ch = CON_INPUT_CURSOR; + } + else + { + ch = (text[i] == '\0') ? 10 : text[i]; + } + } + else if (i >= txtlen) + { + ch = ' '; + } + else + { + ch = text[i]; + } + + Draw_CharScaled(((i + 1 + draw_icon) << 3) * scale, con.vislines - 22 * scale, ch, scale); + } } /* diff --git a/src/client/cl_keyboard.c b/src/client/cl_keyboard.c index 5b00a0a3..fc05b228 100644 --- a/src/client/cl_keyboard.c +++ b/src/client/cl_keyboard.c @@ -29,6 +29,10 @@ #include "header/client.h" +void Key_ClearTyping(void); +void IN_GetClipboardText(char *out, size_t n); +int IN_SetClipboardText(const char *s); + static cvar_t *cfg_unbindall; /* @@ -268,9 +272,10 @@ keyname_t keynames[] = { void CompleteCommand(void) { - char *cmd, *s; + char *cmd; + char *s; - s = key_lines[edit_line] + 1; + s = key_lines[edit_line]; if ((*s == '\\') || (*s == '/')) { @@ -278,74 +283,68 @@ CompleteCommand(void) } cmd = Cmd_CompleteCommand(s); - - if (cmd) + if (!cmd) { - key_lines[edit_line][1] = '/'; - strcpy(key_lines[edit_line] + 2, cmd); - key_linepos = strlen(cmd) + 2; - - if (Cmd_IsComplete(cmd)) - { - key_lines[edit_line][key_linepos] = ' '; - key_linepos++; - key_lines[edit_line][key_linepos] = 0; - } - else - { - key_lines[edit_line][key_linepos] = 0; - } + return; } - return; + *key_lines[edit_line] = '/'; + strcpy(key_lines[edit_line] + 1, cmd); + key_linepos = strlen(cmd) + 1; + + if (Cmd_IsComplete(cmd)) + { + key_lines[edit_line][key_linepos] = ' '; + key_linepos++; + } + + key_lines[edit_line][key_linepos] = '\0'; } void CompleteMapNameCommand(void) { - int i; - char *s, *t, *cmdArg; + char *s, *cmdArg; const char *mapCmdString = "map "; - s = key_lines[edit_line] + 1; + s = key_lines[edit_line]; if ((*s == '\\') || (*s == '/')) { s++; } - t = s; - - for (i = 0; i < strlen(mapCmdString); i++) + if (strncmp(mapCmdString, s, strlen(mapCmdString)) != 0) { - if (t[i] == mapCmdString[i]) - { - s++; - } - else - { - return; - } + return; } - cmdArg = Cmd_CompleteMapCommand(s); + cmdArg = Cmd_CompleteMapCommand(s + strlen(mapCmdString)); if (cmdArg) { - key_lines[edit_line][1] = '/'; - strcpy(key_lines[edit_line] + 2, mapCmdString); + snprintf(key_lines[edit_line], MAXCMDLINE, "/%s%s", mapCmdString, cmdArg); key_linepos = strlen(key_lines[edit_line]); - strcpy(key_lines[edit_line] + key_linepos, cmdArg); - key_linepos = key_linepos + strlen(cmdArg); } } /* * Interactive line editing and console scrollback */ +static int +IsInConsole(void) +{ + return cls.key_dest == key_console || + (cls.key_dest == key_game && + (cls.state == ca_disconnected || cls.state == ca_connecting)); +} + void Key_Console(int key) { + char txt[2]; + char cliptext[256]; + /* * Ignore keypad in console to prevent duplicate * entries through key presses processed as a @@ -375,35 +374,63 @@ Key_Console(int key) break; } - if (key == 'l') + if (keydown[K_CTRL]) { - if (keydown[K_CTRL]) + if (key == 'l') { Cbuf_AddText("clear\n"); return; } + + if (key == 'c' || key == 'x') + { + if (*key_lines[edit_line] != '\0') + { + if (IN_SetClipboardText(key_lines[edit_line])) + { + Com_Printf("Copy to clipboard failed.\n"); + } + else if (key == 'x') + { + Key_ClearTyping(); + } + } + + return; + } + + if (key == 'v') + { + IN_GetClipboardText(cliptext, sizeof(cliptext)); + + if (*cliptext != '\0') + { + key_linepos += Q_strins(key_lines[edit_line], cliptext, key_linepos, MAXCMDLINE); + } + + return; + } } if ((key == K_ENTER) || (key == K_KP_ENTER)) { /* slash text are commands, else chat */ - if ((key_lines[edit_line][1] == '\\') || - (key_lines[edit_line][1] == '/')) + if ((*key_lines[edit_line] == '\\') || (*key_lines[edit_line] == '/')) { - Cbuf_AddText(key_lines[edit_line] + 2); /* skip the > */ + Cbuf_AddText(key_lines[edit_line] + 1); /* skip the > */ } else { - Cbuf_AddText(key_lines[edit_line] + 1); /* valid command */ + Cbuf_AddText(key_lines[edit_line]); /* valid command */ } Cbuf_AddText("\n"); - Com_Printf("%s\n", key_lines[edit_line]); + Com_Printf("%c%s\n", CON_INPUT_INDICATOR, key_lines[edit_line]); + edit_line = (edit_line + 1) & (NUM_KEY_LINES - 1); history_line = edit_line; - key_lines[edit_line][0] = ']'; - key_lines[edit_line][1] = '\0'; - key_linepos = 1; + + Key_ClearTyping(); if (cls.state == ca_disconnected) { @@ -423,12 +450,32 @@ Key_Console(int key) return; } - if ((key == K_BACKSPACE) || (key == K_LEFTARROW) || - (key == K_KP_LEFTARROW) || + if (key == K_LEFTARROW) + { + if (key_linepos > 0) + { + key_linepos--; + } + + return; + } + + if (key == K_RIGHTARROW) + { + if (key_lines[edit_line][key_linepos] != '\0') + { + key_linepos++; + } + + return; + } + + if ((key == K_BACKSPACE) || ((key == 'h') && (keydown[K_CTRL]))) { - if (key_linepos > 1) + if (key_linepos > 0) { + Q_strdel(key_lines[edit_line], key_linepos - 1, 1); key_linepos--; } @@ -437,9 +484,11 @@ Key_Console(int key) if (key == K_DEL) { - memmove(key_lines[edit_line] + key_linepos, - key_lines[edit_line] + key_linepos + 1, - sizeof(key_lines[edit_line]) - key_linepos - 1); + if (key_lines[edit_line][key_linepos] != '\0') + { + Q_strdel(key_lines[edit_line], key_linepos, 1); + } + return; } @@ -451,7 +500,7 @@ Key_Console(int key) history_line = (history_line - 1) & (NUM_KEY_LINES-1); } while (history_line != edit_line && - !key_lines[history_line][1]); + key_lines[history_line][0] == '\0'); if (history_line == edit_line) { @@ -459,7 +508,8 @@ Key_Console(int key) } memmove(key_lines[edit_line], key_lines[history_line], sizeof(key_lines[edit_line])); - key_linepos = (int)strlen(key_lines[edit_line]); + key_linepos = strlen(key_lines[edit_line]); + return; } @@ -476,17 +526,16 @@ Key_Console(int key) history_line = (history_line + 1) & (NUM_KEY_LINES-1); } while (history_line != edit_line && - !key_lines[history_line][1]); + key_lines[history_line][0] == '\0'); if (history_line == edit_line) { - key_lines[edit_line][0] = ']'; - key_linepos = 1; + Key_ClearTyping(); } else { memmove(key_lines[edit_line], key_lines[history_line], sizeof(key_lines[edit_line])); - key_linepos = (int)strlen(key_lines[edit_line]); + key_linepos = strlen(key_lines[edit_line]); } return; @@ -521,7 +570,7 @@ Key_Console(int key) else { - key_linepos = 1; + key_linepos = 0; } return; @@ -536,7 +585,7 @@ Key_Console(int key) else { - key_linepos = (int)strlen(key_lines[edit_line]); + key_linepos = strlen(key_lines[edit_line]); } return; @@ -547,32 +596,10 @@ Key_Console(int key) return; /* non printable character */ } - if (key_linepos < MAXCMDLINE - 1) - { - int last; - int length; + *txt = key; + *(txt + 1) = '\0'; - length = strlen(key_lines[edit_line]); - - if (length >= MAXCMDLINE - 1) - { - return; - } - - last = key_lines[edit_line][key_linepos]; - - memmove(key_lines[edit_line] + key_linepos + 1, - key_lines[edit_line] + key_linepos, - length - key_linepos); - - key_lines[edit_line][key_linepos] = key; - key_linepos++; - - if (!last) - { - key_lines[edit_line][key_linepos] = 0; - } - } + key_linepos += Q_strins(key_lines[edit_line], txt, key_linepos, MAXCMDLINE); } qboolean chat_team; @@ -953,7 +980,7 @@ Key_WriteConsoleHistory() int lineIdx = (edit_line+i) & (NUM_KEY_LINES-1); const char* line = key_lines[lineIdx]; - if(line[1] != '\0' && strcmp(lastWrittenLine, line ) != 0) + if(*line != '\0' && strcmp(lastWrittenLine, line) != 0) { // if the line actually contains something besides the ] prompt, // and is not identical to the last written line, write it to the file @@ -1000,6 +1027,7 @@ Key_ReadConsoleHistory() history_line = i; break; } + // remove trailing newlines int lastCharIdx = strlen(key_lines[i])-1; while((key_lines[i][lastCharIdx] == '\n' || key_lines[i][lastCharIdx] == '\r') && lastCharIdx >= 0) @@ -1007,8 +1035,17 @@ Key_ReadConsoleHistory() key_lines[i][lastCharIdx] = '\0'; --lastCharIdx; } + + /* backwards compatibility with old history files */ + if(key_lines[i][0] == ']') + { + memmove(key_lines[i], key_lines[i] + 1, MAXCMDLINE - 1); + } } + /* don't remember the input line */ + Key_ClearTyping(); + fclose(f); } @@ -1032,12 +1069,11 @@ Key_Init(void) int i; for (i = 0; i < NUM_KEY_LINES; i++) { - key_lines[i][0] = ']'; - key_lines[i][1] = 0; + key_lines[i][0] = '\0'; } // can't call Key_ReadConsoleHistory() here because FS_Gamedir() isn't set yet - key_linepos = 1; + key_linepos = 0; /* init 128 bit ascii characters in console mode */ for (i = 32; i < 128; i++) @@ -1045,6 +1081,7 @@ Key_Init(void) consolekeys[i] = true; } + consolekeys[K_DEL] = true; consolekeys[K_ENTER] = true; consolekeys[K_KP_ENTER] = true; consolekeys[K_TAB] = true; @@ -1380,6 +1417,15 @@ Key_Event(int key, qboolean down, qboolean special) return; } + /* FIXME: Better way to do CTRL+ actions in the console? + special should be set to true in this case. + */ + if (keydown[K_CTRL] && IsInConsole() && + key >= 'a' && key <= 'z') + { + special = true; + } + /* All input subsystems handled after this point only care for key down events (=> if(!down) returns above). */ diff --git a/src/client/header/console.h b/src/client/header/console.h index 432b76ad..1ce0062f 100644 --- a/src/client/header/console.h +++ b/src/client/header/console.h @@ -27,6 +27,9 @@ #ifndef CL_HEADER_CONSOLE_H #define CL_HEADER_CONSOLE_H +#define CON_INPUT_INDICATOR ']' +#define CON_INPUT_CURSOR 11 + #define NUM_CON_TIMES 4 #define CON_TEXTSIZE 32768 diff --git a/src/client/input/header/input.h b/src/client/input/header/input.h index 7c9656f5..9fb73bc8 100644 --- a/src/client/input/header/input.h +++ b/src/client/input/header/input.h @@ -59,4 +59,12 @@ void IN_Update(void); */ void In_FlushQueue(void); +/* Clipboard get/set */ + +/* Copy clipboard to buffer of size n */ +void IN_GetClipboardText(char *out, size_t n); + +/* Copy text to clipboard */ +int IN_SetClipboardText(const char *s); + #endif diff --git a/src/client/input/sdl2.c b/src/client/input/sdl2.c index 520c134a..050c8ae2 100644 --- a/src/client/input/sdl2.c +++ b/src/client/input/sdl2.c @@ -2412,3 +2412,25 @@ IN_Shutdown(void) } /* ------------------------------------------------------------------ */ + +void +IN_GetClipboardText(char *out, size_t n) +{ + char *s = SDL_GetClipboardText(); + + if (!s || *s == '\0') + { + *out = '\0'; + return; + } + + Q_strlcpy(out, s, n - 1); + + SDL_free(s); +} + +int +IN_SetClipboardText(const char *s) +{ + return SDL_SetClipboardText(s); +} diff --git a/src/client/input/sdl3.c b/src/client/input/sdl3.c index 5972c708..40e778c0 100644 --- a/src/client/input/sdl3.c +++ b/src/client/input/sdl3.c @@ -2410,3 +2410,25 @@ IN_Shutdown(void) } /* ------------------------------------------------------------------ */ + +void +IN_GetClipboardText(char *out, size_t n) +{ + char *s = SDL_GetClipboardText(); + + if (!s || *s == '\0') + { + *out = '\0'; + return; + } + + Q_strlcpy(out, s, n - 1); + + SDL_free(s); +} + +int +IN_SetClipboardText(const char *s) +{ + return SDL_SetClipboardText(s); +} diff --git a/src/common/header/shared.h b/src/common/header/shared.h index 109c9fd1..35939b90 100644 --- a/src/common/header/shared.h +++ b/src/common/header/shared.h @@ -329,6 +329,13 @@ char *Q_strlwr(char *s); int Q_strlcpy(char *dst, const char *src, int size); int Q_strlcat(char *dst, const char *src, int size); +/* Delete n characters from s starting at index i */ +void Q_strdel(char *s, size_t i, size_t n); + +/* Insert src into dest starting at index i, total, n is the total size of the buffer */ +/* Returns length of src on success, 0 if there is not enough space in dest for src */ +size_t Q_strins(char *dest, const char *src, size_t i, size_t n); + /* ============================================= */ /* Unicode wrappers that also make sure it's a regular file around fopen(). */ diff --git a/src/common/shared/shared.c b/src/common/shared/shared.c index 33a1e2e1..792ffde8 100644 --- a/src/common/shared/shared.c +++ b/src/common/shared/shared.c @@ -1144,6 +1144,52 @@ Q_strlcat(char *dst, const char *src, int size) return (d - dst) + Q_strlcpy(d, src, size); } +void +Q_strdel(char *s, size_t i, size_t n) +{ + size_t len; + + if (!n) + { + return; + } + + len = strlen(s); + + if (i >= len || n > (len - i)) + { + return; + } + + memmove(s + i, s + i + n, len - i); + s[len - n] = '\0'; +} + +size_t +Q_strins(char *dest, const char *src, size_t i, size_t n) +{ + size_t dlen; + size_t slen; + + if (!src || *src == '\0') + { + return 0; + } + + slen = strlen(src); + dlen = strlen(dest); + + if (i > dlen || (dlen + slen + 1) > n) + { + return 0; + } + + memmove(dest + i + slen, dest + i, dlen - i + 1); + memcpy(dest + i, src, slen); + + return slen; +} + /* * An unicode compatible fopen() Wrapper for Windows. */