Merge pull request #357 from mihmig/master
Add far2l terminal extensions support
This commit is contained in:
@@ -1295,8 +1295,8 @@ void load_open_settings(settings_r *sesskey, Conf *conf)
|
||||
gppb(sesskey, "HideMousePtr", false, conf, CONF_hide_mouseptr);
|
||||
gppb(sesskey, "SunkenEdge", false, conf, CONF_sunken_edge);
|
||||
gppi(sesskey, "WindowBorder", 1, conf, CONF_window_border);
|
||||
gppi(sesskey, "CurType", 0, conf, CONF_cursor_type);
|
||||
gppb(sesskey, "BlinkCur", false, conf, CONF_blink_cur);
|
||||
gppi(sesskey, "CurType", 1, conf, CONF_cursor_type);
|
||||
gppb(sesskey, "BlinkCur", true, conf, CONF_blink_cur);
|
||||
/* pedantic compiler tells me I can't use conf, CONF_beep as an int * :-) */
|
||||
gppi(sesskey, "Beep", 1, conf, CONF_beep);
|
||||
gppi(sesskey, "BeepInd", 0, conf, CONF_beep_ind);
|
||||
|
||||
+592
-1
@@ -13,6 +13,10 @@
|
||||
#include "putty.h"
|
||||
#include "terminal.h"
|
||||
|
||||
/* base64 library - needed for far2l extensions support */
|
||||
#include <windows/cencode.h>
|
||||
#include <windows/cdecode.h>
|
||||
|
||||
#ifdef MOD_PERSO
|
||||
#include "charset.h"
|
||||
int get_param( const char * val ) ;
|
||||
@@ -2000,6 +2004,10 @@ Terminal *term_init(Conf *myconf, struct unicode_data *ucsdata, TermWin *win)
|
||||
* that need it.
|
||||
*/
|
||||
term = snew(Terminal);
|
||||
/* far2l */
|
||||
term->far2l_ext = 0; // far2l extensions mode is enabled?
|
||||
term->is_apc = 0; // currently processing incoming APC sequence?
|
||||
term->clip_allowed = -1; // remote clipboard access is enabled?
|
||||
term->win = win;
|
||||
term->ucsdata = ucsdata;
|
||||
term->conf = conf_copy(myconf);
|
||||
@@ -3204,6 +3212,577 @@ static void toggle_mode(Terminal *term, int mode, int query, bool state)
|
||||
*/
|
||||
static void do_osc(Terminal *term)
|
||||
{
|
||||
/* far2l extensions support */
|
||||
if (term->is_apc) {
|
||||
|
||||
#ifndef _WINDOWS
|
||||
#define DWORD unsigned int
|
||||
#define WORD unsigned short
|
||||
#endif
|
||||
|
||||
if (strncmp(term->osc_string, "far2l", 5) == 0) {
|
||||
|
||||
// okay, this is far2l terminal extensions APC sequence
|
||||
|
||||
// for more info about far2l terminal extensions please see:
|
||||
// https://github.com/cyd01/KiTTY/issues/74
|
||||
// https://github.com/elfmz/far2l/blob/master/WinPort/FarTTY.h
|
||||
|
||||
if (strncmp(term->osc_string+5, "1", 1) == 0) {
|
||||
|
||||
// extensions mode on
|
||||
|
||||
term->far2l_ext = 1;
|
||||
|
||||
char *reply_buf = dupprintf(
|
||||
"\x1b_far2lok\x07");
|
||||
ldisc_send(term->ldisc, reply_buf, strlen(reply_buf),
|
||||
false);
|
||||
sfree(reply_buf);
|
||||
|
||||
// reset clipboard state; todo: do it on session init!
|
||||
term->clip_allowed = -1;
|
||||
|
||||
} else if (strncmp(term->osc_string+5, "0", 1) == 0) {
|
||||
|
||||
// extensions mode off
|
||||
|
||||
term->far2l_ext = 0;
|
||||
|
||||
// reset clipboard state; todo: do it on session init!
|
||||
term->clip_allowed = -1;
|
||||
|
||||
} else if (strncmp(term->osc_string+5, ":", 1) == 0) {
|
||||
|
||||
// processing actual payload
|
||||
|
||||
// base64-decode payload
|
||||
base64_decodestate _d_state;
|
||||
base64_init_decodestate(&_d_state);
|
||||
char* d_out = malloc(term->osc_strlen);
|
||||
int d_count = base64_decode_block(
|
||||
term->osc_string+6, term->osc_strlen-6, d_out, &_d_state);
|
||||
|
||||
// last byte is id
|
||||
BYTE id = d_out[d_count-1];
|
||||
char* reply = 0;
|
||||
int reply_size = 0;
|
||||
|
||||
if (term->osc_strlen == OSC_STR_MAX) {
|
||||
|
||||
// it's possibly too large clipboard
|
||||
|
||||
// we can't deal with it
|
||||
// until implementing dynamic osc_string allocation
|
||||
|
||||
#ifdef _WINDOWS
|
||||
MessageBox(wgs.term_hwnd, "Too large clipboard :(", "Error", MB_OK);
|
||||
#endif
|
||||
|
||||
// correct request id is lost forever
|
||||
// so we can not prevent far2l from hanging
|
||||
// so sad
|
||||
|
||||
// fixme: good idea is to free all allocated memory here, though
|
||||
exit(100);
|
||||
}
|
||||
|
||||
DWORD len;
|
||||
|
||||
// next from the end byte is command
|
||||
switch (d_out[d_count-2]) {
|
||||
|
||||
case 'f':; // FARTTY_INTERRACT_SET_FKEY_TITLES
|
||||
|
||||
reply_size = 5;
|
||||
reply = malloc(reply_size);
|
||||
|
||||
// fixme: unimplemented
|
||||
DWORD zero = 0;
|
||||
|
||||
memcpy(reply, &zero, sizeof(DWORD));
|
||||
|
||||
break;
|
||||
|
||||
case 'n':; // FARTTY_INTERRACT_DESKTOP_NOTIFICATION
|
||||
|
||||
/* // not ready yet
|
||||
|
||||
// todo: generate some reply
|
||||
// todo: show notification only if window is out of focus
|
||||
// todo: remove icon after notification timeout or by mouse click
|
||||
|
||||
// title length, source, utf8, no zero-terminate, bytes
|
||||
DWORD len1;
|
||||
memcpy(&len1, d_out+d_count-6, sizeof(len1));
|
||||
|
||||
// text length, source, utf8, no zero-terminate, bytes
|
||||
DWORD len2;
|
||||
memcpy(&len2, d_out+d_count-6-4-len1, sizeof(len2));
|
||||
|
||||
// destination (wide char)
|
||||
LPWSTR text_wc, title_wc;
|
||||
int textsz_wc, titlesz_wc;
|
||||
|
||||
// notification may contain file names in non-latin
|
||||
// or may have localized title
|
||||
// so we can not assume ascii here and should do
|
||||
// full utf8->multibyte conversion
|
||||
|
||||
titlesz_wc = MultiByteToWideChar(CP_UTF8, 0, (LPCCH)(d_out+len2+4), len1, 0, 0);
|
||||
textsz_wc = MultiByteToWideChar(CP_UTF8, 0, (LPCCH)d_out, len2, 0, 0);
|
||||
|
||||
if (titlesz_wc && textsz_wc) {
|
||||
title_wc = malloc((titlesz_wc+1)*sizeof(wchar_t));
|
||||
MultiByteToWideChar(CP_UTF8, 0, (LPCCH)(d_out+len2+4), len1, title_wc, titlesz_wc);
|
||||
text_wc = malloc((textsz_wc+1)*sizeof(wchar_t));
|
||||
MultiByteToWideChar(CP_UTF8, 0, (LPCCH)d_out, len2, text_wc, textsz_wc);
|
||||
|
||||
title_wc[titlesz_wc] = 0;
|
||||
text_wc[textsz_wc] = 0;
|
||||
|
||||
NOTIFYICONDATAW pnid;
|
||||
|
||||
// todo: do this on window focus also
|
||||
pnid.cbSize = sizeof(pnid);
|
||||
pnid.hWnd = wgs.term_hwnd;
|
||||
pnid.hIcon = LoadIcon(0, IDI_APPLICATION);
|
||||
pnid.uID = 200;
|
||||
Shell_NotifyIconW(NIM_DELETE, &pnid);
|
||||
|
||||
// todo: use putty icon
|
||||
pnid.cbSize = sizeof(pnid);
|
||||
pnid.hWnd = wgs.term_hwnd;
|
||||
pnid.hIcon = LoadIcon(0, IDI_APPLICATION);
|
||||
pnid.uID = 200;
|
||||
pnid.uFlags = NIF_ICON | NIF_INFO | NIF_MESSAGE;
|
||||
pnid.uCallbackMessage = (WM_USER + 200);
|
||||
pnid.dwInfoFlags = NIIF_INFO | NIIF_NOSOUND;
|
||||
memcpy(pnid.szInfoTitle, title_wc, (titlesz_wc+1)*sizeof(wchar_t));
|
||||
memcpy(pnid.szInfo, text_wc, (textsz_wc+1)*sizeof(wchar_t));
|
||||
Shell_NotifyIconW(NIM_ADD, &pnid);
|
||||
|
||||
free(text_wc);
|
||||
free(title_wc);
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
break;
|
||||
|
||||
case 'w': // FARTTY_INTERRACT_GET_WINDOW_MAXSIZE
|
||||
|
||||
// get largest console window size
|
||||
|
||||
reply_size = 5;
|
||||
reply = malloc(reply_size);
|
||||
|
||||
// fixme: unimplemented
|
||||
// here should be short x and short y
|
||||
DWORD none = 0;
|
||||
|
||||
memcpy(reply, &none, sizeof(DWORD));
|
||||
|
||||
break;
|
||||
|
||||
case 'c': // FARTTY_INTERRACT_CLIPBOARD
|
||||
|
||||
// clipboard interaction
|
||||
// next from the end byte is subcommand
|
||||
|
||||
switch (d_out[d_count-3]) {
|
||||
|
||||
case 'r':; // FARTTY_INTERRACT_CLIP_REGISTER_FORMAT
|
||||
|
||||
// register format
|
||||
|
||||
memcpy(&len, d_out + d_count - 3 - 4, sizeof(DWORD));
|
||||
d_out[len] = 0; // zero-terminate format name
|
||||
|
||||
#ifdef _WINDOWS
|
||||
// far2l sends format name as (utf8?) string,
|
||||
// which actually containing ascii only
|
||||
// so we can just call ascii function
|
||||
uint32_t status = RegisterClipboardFormatA(d_out);
|
||||
#endif
|
||||
|
||||
reply_size = 5;
|
||||
reply = malloc(reply_size);
|
||||
|
||||
#ifdef _WINDOWS
|
||||
memcpy(reply, &status, sizeof(uint32_t));
|
||||
#else
|
||||
bzero(reply, sizeof(uint32_t));
|
||||
#endif
|
||||
|
||||
break;
|
||||
|
||||
case 'e':; // FARTTY_INTERRACT_CLIP_EMPTY
|
||||
|
||||
#ifdef _WINDOWS
|
||||
char ec_status = 0;
|
||||
if (term->clip_allowed == 1) {
|
||||
OpenClipboard(wgs.term_hwnd);
|
||||
ec_status = EmptyClipboard() ? 1 : 0;
|
||||
CloseClipboard();
|
||||
}
|
||||
#endif
|
||||
|
||||
reply_size = 2;
|
||||
reply = malloc(reply_size);
|
||||
|
||||
#ifdef _WINDOWS
|
||||
reply[0] = ec_status;
|
||||
#else
|
||||
reply[0] = 0;
|
||||
#endif
|
||||
|
||||
break;
|
||||
|
||||
case 'a':; // FARTTY_INTERRACT_CLIP_ISAVAIL
|
||||
|
||||
uint32_t a_fmt;
|
||||
memcpy(&a_fmt, d_out + d_count - 3 - 4, sizeof(uint32_t));
|
||||
|
||||
#ifdef _WINDOWS
|
||||
char out = IsClipboardFormatAvailable(a_fmt) ? 1 : 0;
|
||||
#endif
|
||||
|
||||
reply_size = 2;
|
||||
reply = malloc(reply_size);
|
||||
|
||||
#ifdef _WINDOWS
|
||||
reply[0] = out;
|
||||
#else
|
||||
reply[0] = 0;
|
||||
#endif
|
||||
|
||||
break;
|
||||
|
||||
case 'o':; // FARTTY_INTERRACT_CLIP_OPEN
|
||||
|
||||
// open
|
||||
// next from the end 4 bytes is client_id length
|
||||
memcpy(&len, d_out + d_count - 3 - 4, sizeof(DWORD));
|
||||
d_out[len] = 0; // all remaining string is client id, make it zero terminated
|
||||
|
||||
// todo: check/store client id, all that stuff
|
||||
|
||||
reply_size = 2;
|
||||
reply = malloc(reply_size);
|
||||
|
||||
#ifdef _WINDOWS
|
||||
if (term->clip_allowed == -1) {
|
||||
int status = MessageBox(wgs.term_hwnd,
|
||||
"Allow far2l clipboard sync?", "PyTTY", MB_OKCANCEL);
|
||||
if (status == IDOK) {
|
||||
term->clip_allowed = 1;
|
||||
} else {
|
||||
// IDCANCEL
|
||||
term->clip_allowed = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// status is first response byte
|
||||
if (term->clip_allowed == 1) {
|
||||
reply[0] = 1;
|
||||
} else {
|
||||
reply[0] = -1;
|
||||
}
|
||||
#else
|
||||
reply[0] = -1;
|
||||
#endif
|
||||
|
||||
break;
|
||||
|
||||
case 's':; // FARTTY_INTERRACT_CLIP_SETDATA
|
||||
|
||||
// set data
|
||||
|
||||
if (term->clip_allowed == 1 && d_count >= 4 + 4 + 3) {
|
||||
|
||||
#ifdef _WINDOWS
|
||||
|
||||
uint32_t fmt;
|
||||
char* buffer = NULL;
|
||||
int BufferSize = 0;
|
||||
memcpy(&fmt, d_out + d_count - 3 - 4, sizeof(uint32_t));
|
||||
|
||||
// id, 'c', 's', 4-byte fmt, next goes 4-byte len
|
||||
memcpy(&len, d_out + d_count - 3 - 4 - 4, sizeof(DWORD));
|
||||
if (len > d_count - 3 - 4 - 4)
|
||||
len = d_count - 3 - 4 - 4;
|
||||
|
||||
if (fmt == CF_TEXT) {
|
||||
int cnt = MultiByteToWideChar(CP_UTF8, 0, (LPCCH)d_out, len, NULL, 0);
|
||||
if (cnt > 0) {
|
||||
buffer = calloc(cnt + 1, sizeof(wchar_t));
|
||||
MultiByteToWideChar(CP_UTF8, 0, (LPCCH)d_out, len, (PWCHAR)buffer, cnt);
|
||||
}
|
||||
fmt = CF_UNICODETEXT;
|
||||
BufferSize = (wcslen((PWCHAR)buffer) + 1) * sizeof(WCHAR);
|
||||
|
||||
} else if (fmt == CF_UNICODETEXT) {
|
||||
// very stupid utf32->utf16 'conversion'
|
||||
buffer = calloc((len / sizeof(uint32_t)) + 1, sizeof(wchar_t));
|
||||
for (int i=0; i < len / sizeof(uint32_t); ++i) {
|
||||
memcpy(
|
||||
&buffer[i * sizeof(wchar_t)],
|
||||
&d_out[i * sizeof(uint32_t)],
|
||||
sizeof(wchar_t)
|
||||
);
|
||||
}
|
||||
BufferSize = (wcslen((PWCHAR)buffer) + 1) * sizeof(WCHAR);
|
||||
|
||||
} else if (fmt >= 0xC000) {
|
||||
// no transcoding - copy it as is
|
||||
buffer = malloc(len);
|
||||
memcpy(buffer, &d_out[0], len);
|
||||
BufferSize = len;
|
||||
}
|
||||
|
||||
// clipboard stuff itself
|
||||
|
||||
HGLOBAL hData;
|
||||
void *GData;
|
||||
|
||||
bool set_successful = 0;
|
||||
|
||||
if (buffer && (hData=GlobalAlloc(GMEM_MOVEABLE,BufferSize))) {
|
||||
|
||||
if ((GData=GlobalLock(hData))) {
|
||||
|
||||
memcpy(GData,buffer,BufferSize);
|
||||
GlobalUnlock(hData);
|
||||
|
||||
if (OpenClipboard(wgs.term_hwnd)) {
|
||||
|
||||
if (!SetClipboardData(fmt, (HANDLE)hData)) {
|
||||
GlobalFree(hData);
|
||||
} else {
|
||||
set_successful = 1;
|
||||
}
|
||||
|
||||
CloseClipboard();
|
||||
|
||||
} else {
|
||||
GlobalFree(hData);
|
||||
}
|
||||
|
||||
} else {
|
||||
GlobalFree(hData);
|
||||
}
|
||||
}
|
||||
|
||||
free(buffer);
|
||||
|
||||
#endif
|
||||
|
||||
// prepare reply
|
||||
reply_size = 2;
|
||||
reply = malloc(reply_size);
|
||||
|
||||
// first reply byte is status
|
||||
#ifdef _WINDOWS
|
||||
reply[0] = set_successful;
|
||||
#else
|
||||
reply[0] = 0;
|
||||
#endif
|
||||
|
||||
} else {
|
||||
|
||||
reply_size = 2;
|
||||
reply = malloc(reply_size);
|
||||
|
||||
reply[0] = 0;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'g':; // FARTTY_INTERRACT_CLIP_GETDATA
|
||||
|
||||
if (term->clip_allowed == 1) {
|
||||
|
||||
#ifdef _WINDOWS
|
||||
|
||||
uint32_t gfmt;
|
||||
memcpy(&gfmt, d_out + d_count - 3 - 4, sizeof(uint32_t));
|
||||
|
||||
// clipboard stuff itself
|
||||
|
||||
void *ClipText = NULL;
|
||||
int32_t ClipTextSize = 0;
|
||||
|
||||
if ((gfmt == CF_TEXT || gfmt == CF_UNICODETEXT || gfmt >= 0xC000) &&
|
||||
OpenClipboard(wgs.term_hwnd))
|
||||
{
|
||||
HANDLE hClipData = GetClipboardData((gfmt == CF_TEXT) ? CF_UNICODETEXT : gfmt);
|
||||
|
||||
if (hClipData)
|
||||
{
|
||||
void *pClipData=GlobalLock(hClipData);
|
||||
|
||||
if (pClipData)
|
||||
{
|
||||
size_t n = GlobalSize(hClipData);
|
||||
|
||||
if (gfmt == CF_TEXT) {
|
||||
|
||||
ClipTextSize = WideCharToMultiByte(
|
||||
CP_UTF8,
|
||||
0,
|
||||
(wchar_t *)pClipData,
|
||||
n,
|
||||
NULL,
|
||||
0,
|
||||
NULL,
|
||||
NULL
|
||||
) + 1;
|
||||
|
||||
if (ClipTextSize >= 0) {
|
||||
n = wcsnlen((wchar_t *)pClipData, n / sizeof(wchar_t));
|
||||
ClipText = calloc(ClipTextSize + 1, 1);
|
||||
if (ClipText) {
|
||||
WideCharToMultiByte(
|
||||
CP_UTF8,
|
||||
0,
|
||||
(wchar_t *)pClipData,
|
||||
n,
|
||||
(char *)ClipText,
|
||||
ClipTextSize,
|
||||
NULL,
|
||||
NULL
|
||||
);
|
||||
ClipTextSize = strlen((char *)ClipText) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (gfmt == CF_UNICODETEXT) {
|
||||
n = wcsnlen((wchar_t *)pClipData, n / sizeof(wchar_t));
|
||||
ClipText = calloc((n + 1), sizeof(uint32_t));
|
||||
if (ClipText) {
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
((uint32_t *)ClipText)[i] = ((uint16_t *)pClipData)[i];
|
||||
}
|
||||
ClipTextSize = (n + 1) * sizeof(uint32_t);
|
||||
}
|
||||
|
||||
} else {
|
||||
ClipText = malloc(n);
|
||||
if (ClipText) {
|
||||
memcpy(ClipText, pClipData, n);
|
||||
ClipTextSize = n;
|
||||
}
|
||||
}
|
||||
|
||||
GlobalUnlock(hClipData);
|
||||
}
|
||||
|
||||
} else {
|
||||
// todo: process errors
|
||||
}
|
||||
CloseClipboard();
|
||||
}
|
||||
|
||||
|
||||
if (!ClipText || ClipTextSize <= 0) {
|
||||
|
||||
// clipboard is empty
|
||||
reply_size = 5; // 4 bytes for size and one for id
|
||||
reply = calloc(1, reply_size);
|
||||
|
||||
} else {
|
||||
|
||||
// + length (4 bytes) + id (1 byte)
|
||||
reply_size = ClipTextSize + 5;
|
||||
reply = calloc(1, reply_size);
|
||||
memcpy(reply, ClipText, ClipTextSize);
|
||||
|
||||
// set size
|
||||
memcpy(reply + ClipTextSize, &ClipTextSize, sizeof(ClipTextSize));
|
||||
}
|
||||
|
||||
free(ClipText);
|
||||
|
||||
#else
|
||||
reply_size = 5;
|
||||
reply = calloc(1, reply_size);
|
||||
#endif
|
||||
|
||||
} else {
|
||||
|
||||
// we should never reach here
|
||||
// anyway, mimic empty clipboard
|
||||
|
||||
reply_size = 5;
|
||||
reply = calloc(1, reply_size);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
free(d_out);
|
||||
|
||||
if (reply_size > 0) {
|
||||
// request is correct and we should send reply
|
||||
|
||||
// last byte is always id
|
||||
reply[reply_size-1] = id;
|
||||
|
||||
// ok, let us try to answer something
|
||||
|
||||
// base64-encode
|
||||
// result in null-terminated char* out
|
||||
base64_encodestate _state;
|
||||
base64_init_encodestate(&_state);
|
||||
|
||||
char* out = malloc(reply_size*2);
|
||||
int count = base64_encode_block((char*)reply, reply_size, out, &_state);
|
||||
// finishing '=' characters
|
||||
char* next_char = out + count;
|
||||
switch (_state.step)
|
||||
{
|
||||
case step_B:
|
||||
*next_char++ = base64_encode_value(_state.result);
|
||||
*next_char++ = '=';
|
||||
*next_char++ = '=';
|
||||
break;
|
||||
case step_C:
|
||||
*next_char++ = base64_encode_value(_state.result);
|
||||
*next_char++ = '=';
|
||||
break;
|
||||
case step_A:
|
||||
break;
|
||||
}
|
||||
count = next_char - out;
|
||||
out[count] = 0;
|
||||
|
||||
// send escape seq
|
||||
|
||||
char* str = "\x1b_far2l";
|
||||
backend_send(term->backend, str, strlen(str));
|
||||
|
||||
backend_send(term->backend, out, count);
|
||||
|
||||
char* str2 = "\x07";
|
||||
backend_send(term->backend, str2, strlen(str2));
|
||||
|
||||
// don't forget to free memory :)
|
||||
free(reply);
|
||||
free(out);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
term->osc_strlen = 0;
|
||||
term->is_apc = 0;
|
||||
|
||||
} else
|
||||
|
||||
if (term->osc_w) {
|
||||
while (term->osc_strlen--)
|
||||
term->wordness[(unsigned char)
|
||||
@@ -4119,7 +4698,15 @@ static void term_out(Terminal *term)
|
||||
term->esc_args[0] = ARG_DEFAULT;
|
||||
term->esc_query = 0;
|
||||
break;
|
||||
case ']': /* OSC: xterm escape sequences */
|
||||
case '_': /* far2l: processing APC is almost the same as processing OSC */
|
||||
term->is_apc = 1;
|
||||
//break;
|
||||
//case SEEN_APC:
|
||||
/* todo */
|
||||
// if (!WriteStr2TC(fdout, enable ? "\x1b_far2l1\x1b\\\x1b[5n" : "\x1b_far2l0\x07\x1b[5n"))
|
||||
// if (!WriteStr2TC(fdout, enable ? "\x1b_far2l1\x1b\\\x1b[5n" : "\x1b_far2l0\x07\x1b[5n"))
|
||||
//break;
|
||||
case ']': /* OSC: xterm escape sequences */
|
||||
/* Compatibility is nasty here, xterm, linux, decterm yuk! */
|
||||
compatibility(OTHER);
|
||||
term->termstate = SEEN_OSC;
|
||||
@@ -5340,6 +5927,10 @@ static void term_out(Terminal *term)
|
||||
} else {
|
||||
term->termstate = OSC_STRING;
|
||||
term->osc_strlen = 0;
|
||||
/* far2l */
|
||||
if (term->is_apc) {
|
||||
term->osc_string[term->osc_strlen++] = (char)c;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
* but for the moment, this will do.
|
||||
*/
|
||||
|
||||
#include <windows/winseat.h>
|
||||
extern WinGuiSeat wgs;
|
||||
|
||||
#ifndef PUTTY_TERMINAL_H
|
||||
#define PUTTY_TERMINAL_H
|
||||
|
||||
@@ -185,11 +188,20 @@ struct terminal_tag {
|
||||
#define ANSI(x,y) ((x)+((y)*256))
|
||||
#define ANSI_QUE(x) ANSI(x,1)
|
||||
|
||||
#define OSC_STR_MAX 2048
|
||||
/* far2l extensions support */
|
||||
//#define OSC_STR_MAX 2048
|
||||
// todo: allocate osc_string dynamically
|
||||
#define OSC_STR_MAX 1048576
|
||||
|
||||
int osc_strlen;
|
||||
char osc_string[OSC_STR_MAX + 1];
|
||||
bool osc_w;
|
||||
|
||||
/* far2l */
|
||||
int far2l_ext; // extensions mode on
|
||||
bool is_apc; // currently processing APC sequence
|
||||
int clip_allowed; // remote clipboard access is allowed
|
||||
|
||||
char id_string[1024];
|
||||
|
||||
unsigned char *tabs;
|
||||
@@ -205,7 +217,8 @@ struct terminal_tag {
|
||||
|
||||
SEEN_OSC_P,
|
||||
OSC_STRING, OSC_MAYBE_ST,
|
||||
VT52_ESC,
|
||||
/* far2l extensions support */ SEEN_APC,
|
||||
VT52_ESC,
|
||||
VT52_Y1,
|
||||
VT52_Y2,
|
||||
VT52_FG,
|
||||
|
||||
@@ -418,6 +418,7 @@ putty.exe: agentf.o aqsync.o be_all_s.o be_misc.o callback.o cmdline.o \
|
||||
winnps.o winpgntc.o winprint.o winproxy.o winsecur.o \
|
||||
winselgui.o winser.o winshare.o winstore.o wintime.o \
|
||||
winucs.o winutils.o winx11.o x11fwd.o \
|
||||
cencode.o cdecode.o \
|
||||
adb.o \
|
||||
kitty.o kitty_commun.o kitty_crypt.o kitty_image.o kitty_proxy.o kitty_registry.o kitty_ssh.o \
|
||||
kitty_store.o kitty_tools.o kitty_win.o \
|
||||
@@ -447,6 +448,7 @@ putty.exe: agentf.o aqsync.o be_all_s.o be_misc.o callback.o cmdline.o \
|
||||
winnps.o winpgntc.o winprint.o winproxy.o winsecur.o \
|
||||
winselgui.o winser.o winshare.o winstore.o wintime.o \
|
||||
winucs.o winutils.o winx11.o x11fwd.o \
|
||||
cencode.o cdecode.o \
|
||||
adb.o \
|
||||
kitty.o kitty_commun.o kitty_crypt.o kitty_image.o kitty_proxy.o kitty_registry.o kitty_ssh.o \
|
||||
kitty_store.o kitty_tools.o kitty_win.o \
|
||||
@@ -528,6 +530,11 @@ testcrypt.exe: ecc.o marshal.o memory.o millerrabin.o mpint.o mpunsafe.o \
|
||||
sshpubk.o sshrsa.o sshrsag.o sshsh256.o sshsh512.o sshsha.o \
|
||||
sshsha3.o testcrypt.o tree234.o utils.o winmiscs.o
|
||||
|
||||
cencode.o: ../windows/cencode.c
|
||||
$(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/cencode.c
|
||||
|
||||
cdecode.o: ../windows/cencode.c
|
||||
$(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/cdecode.c
|
||||
|
||||
agentf.o: ../agentf.c ../putty.h ../ssh.h ../pageant.h ../sshchan.h \
|
||||
../defs.h ../puttyps.h ../network.h ../misc.h ../marshal.h \
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
cdecoder.c - c source to a base64 decoding algorithm implementation
|
||||
|
||||
This is part of the libb64 project, and has been placed in the public domain.
|
||||
For details, see http://sourceforge.net/projects/libb64
|
||||
*/
|
||||
|
||||
#include <cdecode.h>
|
||||
|
||||
int base64_decode_value(char value_in)
|
||||
{
|
||||
static const char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51};
|
||||
static const char decoding_size = sizeof(decoding);
|
||||
value_in -= 43;
|
||||
if (value_in < 0 || value_in > decoding_size) return -1;
|
||||
return decoding[(int)value_in];
|
||||
}
|
||||
|
||||
void base64_init_decodestate(base64_decodestate* state_in)
|
||||
{
|
||||
state_in->step = step_a;
|
||||
state_in->plainchar = 0;
|
||||
}
|
||||
|
||||
int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in)
|
||||
{
|
||||
const char* codechar = code_in;
|
||||
char* plainchar = plaintext_out;
|
||||
char fragment;
|
||||
|
||||
*plainchar = state_in->plainchar;
|
||||
|
||||
switch (state_in->step)
|
||||
{
|
||||
while (1)
|
||||
{
|
||||
case step_a:
|
||||
do {
|
||||
if (codechar == code_in+length_in)
|
||||
{
|
||||
state_in->step = step_a;
|
||||
state_in->plainchar = *plainchar;
|
||||
return plainchar - plaintext_out;
|
||||
}
|
||||
fragment = (char)base64_decode_value(*codechar++);
|
||||
} while (fragment < 0);
|
||||
*plainchar = (fragment & 0x03f) << 2;
|
||||
case step_b:
|
||||
do {
|
||||
if (codechar == code_in+length_in)
|
||||
{
|
||||
state_in->step = step_b;
|
||||
state_in->plainchar = *plainchar;
|
||||
return plainchar - plaintext_out;
|
||||
}
|
||||
fragment = (char)base64_decode_value(*codechar++);
|
||||
} while (fragment < 0);
|
||||
*plainchar++ |= (fragment & 0x030) >> 4;
|
||||
*plainchar = (fragment & 0x00f) << 4;
|
||||
case step_c:
|
||||
do {
|
||||
if (codechar == code_in+length_in)
|
||||
{
|
||||
state_in->step = step_c;
|
||||
state_in->plainchar = *plainchar;
|
||||
return plainchar - plaintext_out;
|
||||
}
|
||||
fragment = (char)base64_decode_value(*codechar++);
|
||||
} while (fragment < 0);
|
||||
*plainchar++ |= (fragment & 0x03c) >> 2;
|
||||
*plainchar = (fragment & 0x003) << 6;
|
||||
case step_d:
|
||||
do {
|
||||
if (codechar == code_in+length_in)
|
||||
{
|
||||
state_in->step = step_d;
|
||||
state_in->plainchar = *plainchar;
|
||||
return plainchar - plaintext_out;
|
||||
}
|
||||
fragment = (char)base64_decode_value(*codechar++);
|
||||
} while (fragment < 0);
|
||||
*plainchar++ |= (fragment & 0x03f);
|
||||
}
|
||||
}
|
||||
/* control should not reach here */
|
||||
return plainchar - plaintext_out;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
cdecode.h - c header for a base64 decoding algorithm
|
||||
|
||||
This is part of the libb64 project, and has been placed in the public domain.
|
||||
For details, see http://sourceforge.net/projects/libb64
|
||||
*/
|
||||
|
||||
#ifndef BASE64_CDECODE_H
|
||||
#define BASE64_CDECODE_H
|
||||
|
||||
typedef enum
|
||||
{
|
||||
step_a, step_b, step_c, step_d
|
||||
} base64_decodestep;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
base64_decodestep step;
|
||||
char plainchar;
|
||||
} base64_decodestate;
|
||||
|
||||
void base64_init_decodestate(base64_decodestate* state_in);
|
||||
|
||||
int base64_decode_value(char value_in);
|
||||
|
||||
int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in);
|
||||
|
||||
#endif /* BASE64_CDECODE_H */
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
cencoder.c - c source to a base64 encoding algorithm implementation
|
||||
|
||||
This is part of the libb64 project, and has been placed in the public domain.
|
||||
For details, see http://sourceforge.net/projects/libb64
|
||||
*/
|
||||
|
||||
#include <cencode.h>
|
||||
|
||||
const int CHARS_PER_LINE = 72;
|
||||
|
||||
void base64_init_encodestate(base64_encodestate* state_in)
|
||||
{
|
||||
state_in->step = step_A;
|
||||
state_in->result = 0;
|
||||
state_in->stepcount = 0;
|
||||
}
|
||||
|
||||
char base64_encode_value(char value_in)
|
||||
{
|
||||
static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
if (value_in > 63) return '=';
|
||||
return encoding[(int)value_in];
|
||||
}
|
||||
|
||||
int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in)
|
||||
{
|
||||
const char* plainchar = plaintext_in;
|
||||
const char* const plaintextend = plaintext_in + length_in;
|
||||
char* codechar = code_out;
|
||||
char result;
|
||||
char fragment;
|
||||
|
||||
result = state_in->result;
|
||||
|
||||
switch (state_in->step)
|
||||
{
|
||||
while (1)
|
||||
{
|
||||
case step_A:
|
||||
if (plainchar == plaintextend)
|
||||
{
|
||||
state_in->result = result;
|
||||
state_in->step = step_A;
|
||||
return codechar - code_out;
|
||||
}
|
||||
fragment = *plainchar++;
|
||||
result = (fragment & 0x0fc) >> 2;
|
||||
*codechar++ = base64_encode_value(result);
|
||||
result = (fragment & 0x003) << 4;
|
||||
case step_B:
|
||||
if (plainchar == plaintextend)
|
||||
{
|
||||
state_in->result = result;
|
||||
state_in->step = step_B;
|
||||
return codechar - code_out;
|
||||
}
|
||||
fragment = *plainchar++;
|
||||
result |= (fragment & 0x0f0) >> 4;
|
||||
*codechar++ = base64_encode_value(result);
|
||||
result = (fragment & 0x00f) << 2;
|
||||
case step_C:
|
||||
if (plainchar == plaintextend)
|
||||
{
|
||||
state_in->result = result;
|
||||
state_in->step = step_C;
|
||||
return codechar - code_out;
|
||||
}
|
||||
fragment = *plainchar++;
|
||||
result |= (fragment & 0x0c0) >> 6;
|
||||
*codechar++ = base64_encode_value(result);
|
||||
result = (fragment & 0x03f) >> 0;
|
||||
*codechar++ = base64_encode_value(result);
|
||||
|
||||
++(state_in->stepcount);
|
||||
if (state_in->stepcount == CHARS_PER_LINE/4)
|
||||
{
|
||||
//*codechar++ = '\n';
|
||||
state_in->stepcount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* control should not reach here */
|
||||
return codechar - code_out;
|
||||
}
|
||||
|
||||
int base64_encode_blockend(char* code_out, base64_encodestate* state_in)
|
||||
{
|
||||
char* codechar = code_out;
|
||||
|
||||
switch (state_in->step)
|
||||
{
|
||||
case step_B:
|
||||
*codechar++ = base64_encode_value(state_in->result);
|
||||
*codechar++ = '=';
|
||||
*codechar++ = '=';
|
||||
break;
|
||||
case step_C:
|
||||
*codechar++ = base64_encode_value(state_in->result);
|
||||
*codechar++ = '=';
|
||||
break;
|
||||
case step_A:
|
||||
break;
|
||||
}
|
||||
//*codechar++ = '\n';
|
||||
|
||||
return codechar - code_out;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
cencode.h - c header for a base64 encoding algorithm
|
||||
|
||||
This is part of the libb64 project, and has been placed in the public domain.
|
||||
For details, see http://sourceforge.net/projects/libb64
|
||||
*/
|
||||
|
||||
#ifndef BASE64_CENCODE_H
|
||||
#define BASE64_CENCODE_H
|
||||
|
||||
typedef enum
|
||||
{
|
||||
step_A, step_B, step_C
|
||||
} base64_encodestep;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
base64_encodestep step;
|
||||
char result;
|
||||
int stepcount;
|
||||
} base64_encodestate;
|
||||
|
||||
void base64_init_encodestate(base64_encodestate* state_in);
|
||||
|
||||
char base64_encode_value(char value_in);
|
||||
|
||||
int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in);
|
||||
|
||||
int base64_encode_blockend(char* code_out, base64_encodestate* state_in);
|
||||
|
||||
#endif /* BASE64_CENCODE_H */
|
||||
+143
-13
@@ -10,6 +10,10 @@
|
||||
#include <limits.h>
|
||||
#include <assert.h>
|
||||
|
||||
/* far2l extensions support - base64 encode/decode libs */
|
||||
#include <cencode.h>
|
||||
#include <cdecode.h>
|
||||
|
||||
#ifdef __WINE__
|
||||
#define NO_MULTIMON /* winelib doesn't have this */
|
||||
#endif
|
||||
@@ -436,7 +440,7 @@ int WINAPI Agent_WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show
|
||||
|
||||
#endif
|
||||
#ifdef MOD_WTS
|
||||
typedef enum _WTS_VIRTUAL_CLASS { WTSVirtualClientData, WTSVirtualFileHandle } WTS_VIRTUAL_CLASS; // WTS_VIRTUAL_CLASS n'est pas défini dans le fichier wtsapi32.h !!!
|
||||
//typedef enum _WTS_VIRTUAL_CLASS { WTSVirtualClientData, WTSVirtualFileHandle } WTS_VIRTUAL_CLASS; // WTS_VIRTUAL_CLASS n'est pas défini dans le fichier wtsapi32.h !!!
|
||||
#include <wtsapi32.h>
|
||||
#endif
|
||||
#if (defined MOD_BACKGROUNDIMAGE) && (!defined FLJ)
|
||||
@@ -612,7 +616,9 @@ static const SeatVtable win_seat_vt = {
|
||||
.interactive = nullseat_interactive_yes,
|
||||
.get_cursor_position = win_seat_get_cursor_position,
|
||||
};
|
||||
static WinGuiSeat wgs = { .seat.vt = &win_seat_vt,
|
||||
// "static" removed by far2l extensions support patch
|
||||
// we need to access wgs from terminal.c to open dialog boxes, etc
|
||||
WinGuiSeat wgs = { .seat.vt = &win_seat_vt,
|
||||
.logpolicy.vt = &win_gui_logpolicy_vt };
|
||||
|
||||
#ifdef MOD_PERSO
|
||||
@@ -4827,10 +4833,17 @@ free(cmd);
|
||||
ignore_clip = wParam; /* don't panic on DESTROYCLIPBOARD */
|
||||
break;
|
||||
case WM_DESTROYCLIPBOARD:
|
||||
if (!ignore_clip)
|
||||
term_lost_clipboard_ownership(term, CLIP_SYSTEM);
|
||||
ignore_clip = false;
|
||||
return 0;
|
||||
/* far2l extensions support */
|
||||
|
||||
// In far2l extensions mode we should not do anything here,
|
||||
// clipboard is handled by far2l extensions.
|
||||
if (!(term->far2l_ext == 1) || (!term->clip_allowed)) {
|
||||
if (!ignore_clip)
|
||||
term_lost_clipboard_ownership(term, CLIP_SYSTEM);
|
||||
ignore_clip = false;
|
||||
}
|
||||
|
||||
return 0;
|
||||
case WM_PAINT: {
|
||||
PAINTSTRUCT p;
|
||||
#if (defined MOD_BACKGROUNDIMAGE) && (!defined FLJ)
|
||||
@@ -5591,13 +5604,130 @@ if( (GetKeyState(VK_MENU)&0x8000) && (wParam==VK_SPACE) ) {
|
||||
*/
|
||||
noise_ultralight(NOISE_SOURCE_KEY, lParam);
|
||||
|
||||
/*
|
||||
* We don't do TranslateMessage since it disassociates the
|
||||
* resulting CHAR message from the KEYDOWN that sparked it,
|
||||
* which we occasionally don't want. Instead, we process
|
||||
* KEYDOWN, and call the Win32 translator functions so that
|
||||
* we get the translations under _our_ control.
|
||||
*/
|
||||
/* far2l extensions support */
|
||||
if (term->far2l_ext) {
|
||||
|
||||
// extensions mode enabled
|
||||
|
||||
// far2l_ext keyboard input event structure
|
||||
WORD repeat; // 2 bytes
|
||||
WORD vkc; // 2 bytes
|
||||
WORD vsc; // 2 bytes
|
||||
DWORD ctrl; // 4 bytes
|
||||
DWORD uchar; // 4 bytes
|
||||
CHAR type; // 1 byte
|
||||
|
||||
// set repeat, virtual keycode, virtual scancode
|
||||
repeat = LOWORD(lParam);
|
||||
vsc = HIWORD(lParam) & 0xFF;
|
||||
vkc = LOWORD(wParam);
|
||||
|
||||
// this fixes far2l's "editor autocomplete" plugin behavior
|
||||
if ((vkc == VK_TAB) || (vkc == VK_BACK) || (vkc == VK_ESCAPE) || (vkc == VK_DELETE)) {
|
||||
vsc = 0;
|
||||
}
|
||||
|
||||
// fixes strange alt+arrows behavior
|
||||
if ((vkc == VK_LEFT) || (vkc == VK_RIGHT) || (vkc == VK_UP) || (vkc == VK_DOWN)) {
|
||||
vsc = 0;
|
||||
}
|
||||
|
||||
// set control keys state
|
||||
ctrl = 0;
|
||||
if (GetAsyncKeyState(VK_LCONTROL)) { ctrl |= LEFT_CTRL_PRESSED; }
|
||||
if (GetAsyncKeyState(VK_RCONTROL)) { ctrl |= RIGHT_CTRL_PRESSED; }
|
||||
if (GetAsyncKeyState(VK_LMENU)) { ctrl |= LEFT_ALT_PRESSED; }
|
||||
if (GetAsyncKeyState(VK_RMENU)) { ctrl |= RIGHT_ALT_PRESSED; }
|
||||
if (GetAsyncKeyState(VK_SHIFT)) { ctrl |= SHIFT_PRESSED; }
|
||||
// begin: reserved for future usage
|
||||
// Console WinAPI does not allow us to distinguish between left and right
|
||||
// shift keys. But PuTTY is not a console app, so why not to send
|
||||
// all information about control keys state that we actually have here?
|
||||
// Using bits not used by any other status for backward compatibility.
|
||||
#define RIGHT_SHIFT_PRESSED 0x1000
|
||||
#define LEFT_SHIFT_PRESSED 0x2000
|
||||
if (GetAsyncKeyState(VK_LSHIFT)) { ctrl |= RIGHT_SHIFT_PRESSED; }
|
||||
if (GetAsyncKeyState(VK_RSHIFT)) { ctrl |= LEFT_SHIFT_PRESSED; }
|
||||
// end
|
||||
if ((lParam & ( 1 << 24 )) >> 24) { ctrl |= ENHANCED_KEY; }
|
||||
if ((((u_short)GetKeyState(VK_NUMLOCK)) & 0xffff) != 0) { ctrl |= NUMLOCK_ON; }
|
||||
if ((((u_short)GetKeyState(VK_SCROLL)) & 0xffff) != 0) { ctrl |= SCROLLLOCK_ON; }
|
||||
if ((((u_short)GetKeyState(VK_CAPITAL)) & 0xffff) != 0) { ctrl |= CAPSLOCK_ON; }
|
||||
|
||||
// set unicode character
|
||||
BYTE kb[256];
|
||||
GetKeyboardState(kb);
|
||||
WCHAR uc[5] = {};
|
||||
ToUnicode(wParam, MapVirtualKey(wParam, MAPVK_VK_TO_VSC), kb, uc, 4, 0);
|
||||
// todo: check result
|
||||
//int result = ToUnicode(wParam, MapVirtualKey(wParam, MAPVK_VK_TO_VSC), kb, uc, 4, 0);
|
||||
uchar = uc[0];
|
||||
|
||||
// set event type
|
||||
if ((message == WM_KEYDOWN) || (message == WM_SYSKEYDOWN)) {
|
||||
type = 'K';
|
||||
} else {
|
||||
type = 'k';
|
||||
}
|
||||
|
||||
char* kev = malloc(15); // keyboard event structure length
|
||||
memcpy(kev, &repeat, sizeof(repeat));
|
||||
memcpy(kev + 2, &vkc, sizeof(vkc));
|
||||
memcpy(kev + 4, &vsc, sizeof(vsc));
|
||||
memcpy(kev + 6, &ctrl, sizeof(ctrl));
|
||||
memcpy(kev + 10, &uchar, sizeof(uchar));
|
||||
memcpy(kev + 14, &type, sizeof(type));
|
||||
|
||||
// base64-encode kev
|
||||
// result in null-terminated char* out
|
||||
base64_encodestate _state;
|
||||
base64_init_encodestate(&_state);
|
||||
char* out = malloc(15*2);
|
||||
int count = base64_encode_block(kev, 15, out, &_state);
|
||||
// finishing '=' characters
|
||||
char* next_char = out + count;
|
||||
switch (_state.step)
|
||||
{
|
||||
case step_B:
|
||||
*next_char++ = base64_encode_value(_state.result);
|
||||
*next_char++ = '=';
|
||||
*next_char++ = '=';
|
||||
break;
|
||||
case step_C:
|
||||
*next_char++ = base64_encode_value(_state.result);
|
||||
*next_char++ = '=';
|
||||
break;
|
||||
case step_A:
|
||||
break;
|
||||
}
|
||||
count = next_char - out;
|
||||
out[count] = 0;
|
||||
|
||||
// send escape seq
|
||||
|
||||
char* str = "\x1b_f2l";
|
||||
backend_send(backend, str, strlen(str));
|
||||
|
||||
backend_send(backend, out, count);
|
||||
|
||||
char* str2 = "\x07";
|
||||
backend_send(backend, str2, strlen(str2));
|
||||
|
||||
// don't forget to free memory :)
|
||||
free(out);
|
||||
|
||||
// we should not do any other key processing in this mode
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* We don't do TranslateMessage since it disassociates the
|
||||
* resulting CHAR message from the KEYDOWN that sparked it,
|
||||
* which we occasionally don't want. Instead, we process
|
||||
* KEYDOWN, and call the Win32 translator functions so that
|
||||
* we get the translations under _our_ control.
|
||||
*/
|
||||
{
|
||||
unsigned char buf[20];
|
||||
int len;
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
* and windlg.c.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
typedef struct WinGuiSeat WinGuiSeat;
|
||||
|
||||
struct WinGuiSeat {
|
||||
|
||||
Reference in New Issue
Block a user