diff --git a/TTXSamples/TTXttyrec/CMakeLists.txt b/TTXSamples/TTXttyrec/CMakeLists.txt index e3cdf2caa..c3175b363 100644 --- a/TTXSamples/TTXttyrec/CMakeLists.txt +++ b/TTXSamples/TTXttyrec/CMakeLists.txt @@ -5,6 +5,8 @@ add_library( TTXttyplay.c gettimeofday.c gettimeofday.h + autofilename.c + autofilename.h ReadMe.txt ReadMe-ja.txt ) @@ -58,6 +60,8 @@ add_library( TTXttyrec.c gettimeofday.c gettimeofday.h + autofilename.c + autofilename.h ReadMe.txt ReadMe-ja.txt ) diff --git a/TTXSamples/TTXttyrec/TTXttyplay.c b/TTXSamples/TTXttyrec/TTXttyplay.c index 44bfaefa9..9cbd5dc5e 100644 --- a/TTXSamples/TTXttyrec/TTXttyplay.c +++ b/TTXSamples/TTXttyrec/TTXttyplay.c @@ -14,6 +14,7 @@ #include "codeconv.h" #include "gettimeofday.h" +#include "autofilename.h" #define ORDER 6001 #define ID_MENU_REPLAY 55302 @@ -113,26 +114,6 @@ void ChangeTitleStatus() { SendMessage(pvar->cv->HWin, WM_COMMAND, MAKELONG(ID_SETUP_WINDOW, 0), 0); } -void ConvertSafeStrFtimeFormat(wchar_t *fmt) { - static const wchar_t *kws=L"aAbBcCdDeFgGhHIjmMnprRStTuUVwWxXyYz%N"; - if (fmt != NULL) { - while (*fmt != L'\0') { - if (*fmt == L'%') { - fmt++; - if (*fmt == L'\0') { - fmt--; - *fmt=L'\0'; - break; - } - if (wcschr(kws, *fmt) == NULL) { - *fmt = L'%'; - } - } - fmt++; - } - } -} - void ChangeTitleTime(struct timeval tv) { char tbuff[TitleBuffSize]; time_t tvs = tv.tv_sec; diff --git a/TTXSamples/TTXttyrec/TTXttyrec.c b/TTXSamples/TTXttyrec/TTXttyrec.c index 3a5190eee..b304d1304 100644 --- a/TTXSamples/TTXttyrec/TTXttyrec.c +++ b/TTXSamples/TTXttyrec/TTXttyrec.c @@ -13,11 +13,13 @@ #include "ttcommdlg.h" #include "gettimeofday.h" +#include "autofilename.h" #define ORDER 6000 #define ID_MENUITEM 55301 #define INISECTION "ttyrec" +#define INISECTIONW L"ttyrec" static HANDLE hInst; /* Instance handle of TTX*.DLL */ @@ -31,6 +33,12 @@ typedef struct { HMENU FileMenu; BOOL record; BOOL rec_stsize; + BOOL rec_auto; + wchar_t *rec_name; + wchar_t *rec_path; + int tel_mode; + int tel_buf_len; + char *tel_buf; } TInstVar; struct recheader { @@ -98,11 +106,34 @@ static void PASCAL TTXInit(PTTSet ts, PComVar cv) { pvar->fh = INVALID_HANDLE_VALUE; pvar->rec_stsize = FALSE; pvar->record = FALSE; + pvar->rec_auto = FALSE; + pvar->rec_name = NULL; + pvar->rec_path = NULL; + pvar->tel_mode = 0; + pvar->tel_buf_len = 32; + pvar->tel_buf = (char*)malloc(pvar->tel_buf_len); } static void PASCAL TTXReadIniFile(const wchar_t *fn, PTTSet ts) { (pvar->origReadIniFile)(fn, ts); pvar->rec_stsize = GetOnOff(INISECTION, "RecordStartSize", fn, TRUE); + pvar->rec_auto = GetOnOff(INISECTION, "RecordAutoStart", fn, FALSE); + + DWORD len = MAX_PATH; + wchar_t *buff = (wchar_t*)malloc(len); + GetPrivateProfileStringW(INISECTIONW, L"RecordDefaultName", L"teraterm.tty", buff, len, fn); + free(pvar->rec_name); + pvar->rec_name = buff; + + buff = (wchar_t*)malloc(len); + GetPrivateProfileStringW(INISECTIONW, L"RecordDefaultPath", L"", buff, len, fn); + free(pvar->rec_path); + pvar->rec_path = buff; +} + +static void PASCAL TTXGetSetupHooks(TTXSetupHooks *hooks) { + pvar->origReadIniFile = *hooks->ReadIniFile; + *hooks->ReadIniFile = TTXReadIniFile; } void WriteData(HANDLE fh, char *buff, int len) { @@ -120,9 +151,97 @@ void WriteData(HANDLE fh, char *buff, int len) { return; } -static void PASCAL TTXGetSetupHooks(TTXSetupHooks *hooks) { - pvar->origReadIniFile = *hooks->ReadIniFile; - *hooks->ReadIniFile = TTXReadIniFile; +void WriteDataTelnet(HANDLE fh, char *buff, int len) { + char *p; + int sz; + + if (pvar->tel_buf_len < len){ + free(pvar->tel_buf); + pvar->tel_buf_len = len; + pvar->tel_buf = (char *)malloc(pvar->tel_buf_len); + } + p = pvar->tel_buf; + for (int i=0; i < len; i ++){ + char ch = buff[i]; + switch (pvar->tel_mode) { + case 0: + if (ch == (char)0xff) { + pvar->tel_mode = 1; + } + else { + *p++ = ch; + } + break; + case 1: + if (ch == (char)0xff) { + *p++ = ch; + pvar->tel_mode = 0; + } + else if ((char)0xfb <= ch && ch <= (char)0xfe) { + pvar->tel_mode = 2; + } + else if (ch == (char)0xfa) { + pvar->tel_mode = 3; + } + else { + pvar->tel_mode = 0; + } + break; + case 2: + pvar->tel_mode = 0; + break; + case 3: + if (ch == (char)0xff) { + pvar->tel_mode = 4; + } + break; + case 4: + if (ch == (char)0xf0) { + pvar->tel_mode = 0; + } + else if (ch != (char)0xff) { + pvar->tel_mode = 3; + } + break; + default: + pvar->tel_mode = 0; + break; + } + } + + sz = (int)(p - pvar->tel_buf); + if (sz > 0) { + WriteData(fh, pvar->tel_buf, sz); + } + + return; +} + +void StartRecording(wchar_t *fname) +{ + pvar->fh = CreateFileW(fname, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (pvar->fh != INVALID_HANDLE_VALUE) { + pvar->record = TRUE; + CheckMenuItem(pvar->FileMenu, ID_MENUITEM, MF_BYCOMMAND | MF_CHECKED); + if (pvar->cv->Ready) { + if (pvar->rec_stsize) { + char buff[20]; + _snprintf_s(buff, sizeof(buff), _TRUNCATE, "\033[8;%d;%dt", + pvar->ts->TerminalHeight, pvar->ts->TerminalWidth); + WriteData(pvar->fh, buff, (int)strlen(buff)); + } + } + } +} + +void StopRecording() +{ + if (pvar->fh != INVALID_HANDLE_VALUE) { + CloseHandle(pvar->fh); + pvar->fh = INVALID_HANDLE_VALUE; + } + pvar->record = FALSE; + CheckMenuItem(pvar->FileMenu, ID_MENUITEM, MF_BYCOMMAND | MF_UNCHECKED); } int PASCAL TTXrecv(SOCKET s, char *buff, int len, int flags) { @@ -130,7 +249,12 @@ int PASCAL TTXrecv(SOCKET s, char *buff, int len, int flags) { rlen = pvar->origPrecv(s, buff, len, flags); if (pvar->record && rlen > 0) { - WriteData(pvar->fh, buff, rlen); + if (pvar->cv->TelFlag) { + WriteDataTelnet(pvar->fh, buff, rlen); + } + else { + WriteData(pvar->fh, buff, rlen); + } } return rlen; } @@ -148,18 +272,33 @@ BOOL PASCAL TTXReadFile(HANDLE fh, LPVOID buff, DWORD len, LPDWORD rbytes, LPOVE static void PASCAL TTXOpenTCP(TTXSockHooks *hooks) { pvar->origPrecv = *hooks->Precv; *hooks->Precv = TTXrecv; + if (pvar->rec_auto) { + wchar_t *_fname; + _fname = GetAutoFilename(pvar->cv, pvar->rec_name, pvar->rec_path, L"teraterm.tty"); + StartRecording(_fname); + free(_fname); + } } static void PASCAL TTXCloseTCP(TTXSockHooks *hooks) { if (pvar->origPrecv) { *hooks->Precv = pvar->origPrecv; } + if (pvar->rec_auto) { + StopRecording(); + } } static void PASCAL TTXOpenFile(TTXFileHooks *hooks) { if (pvar->cv->PortType == IdSerial) { pvar->origPReadFile = *hooks->PReadFile; *hooks->PReadFile = TTXReadFile; + if (pvar->rec_auto) { + wchar_t *_fname; + _fname = GetAutoFilename(pvar->cv, pvar->rec_name, pvar->rec_path, L"teraterm.tty"); + StartRecording(_fname); + free(_fname); + } } } @@ -167,6 +306,9 @@ static void PASCAL TTXCloseFile(TTXFileHooks *hooks) { if (pvar->origPReadFile) { *hooks->PReadFile = pvar->origPReadFile; } + if (pvar->rec_auto) { + StopRecording(); + } } static void PASCAL TTXModifyMenu(HMENU menu) { @@ -199,21 +341,14 @@ static int PASCAL TTXProcessCommand(HWND hWin, WORD cmd) { if (cmd==ID_MENUITEM) { if (pvar->record) { - if (pvar->fh != INVALID_HANDLE_VALUE) { - CloseHandle(pvar->fh); - pvar->fh = INVALID_HANDLE_VALUE; - } - pvar->record = FALSE; - CheckMenuItem(pvar->FileMenu, ID_MENUITEM, MF_BYCOMMAND | MF_UNCHECKED); + StopRecording(); } else { TTOPENFILENAMEW ofn; wchar_t *fname; wchar_t *uimsg1, *uimsg2; - if (pvar->fh != INVALID_HANDLE_VALUE) { - CloseHandle(pvar->fh); - } + StopRecording(); GetI18nStrWW("TTXttyrec", "FILE_FILTER", L"ttyrec(*.tty)\\0*.tty\\0All files(*.*)\\0*.*\\0\\0", pvar->ts->UILanguageFileW, &uimsg1); GetI18nStrWW("TTXttyrec", "FILE_DEFAULTEXTENSION", L"tty", pvar->ts->UILanguageFileW, &uimsg2); @@ -226,18 +361,8 @@ static int PASCAL TTXProcessCommand(HWND hWin, WORD cmd) //ofn.lpstrTitle = L""; ofn.Flags = OFN_OVERWRITEPROMPT; if (TTGetSaveFileNameW(&ofn, &fname)) { - pvar->fh = CreateFileW(fname, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + StartRecording(fname); free(fname); - if (pvar->fh != INVALID_HANDLE_VALUE) { - pvar->record = TRUE; - CheckMenuItem(pvar->FileMenu, ID_MENUITEM, MF_BYCOMMAND | MF_CHECKED); - if (pvar->rec_stsize) { - char buff[20]; - _snprintf_s(buff, sizeof(buff), _TRUNCATE, "\033[8;%d;%dt", - pvar->ts->TerminalHeight, pvar->ts->TerminalWidth); - WriteData(pvar->fh, buff, (int)strlen(buff)); - } - } } free(uimsg1); free(uimsg2); @@ -248,10 +373,11 @@ static int PASCAL TTXProcessCommand(HWND hWin, WORD cmd) } static void PASCAL TTXEnd(void) { - if (pvar->fh != INVALID_HANDLE_VALUE) { - CloseHandle(pvar->fh); - pvar->fh = INVALID_HANDLE_VALUE; - } + StopRecording(); + free(pvar->tel_buf); + pvar->tel_buf = NULL; + free(pvar->rec_name); + pvar->rec_name = NULL; } static TTXExports Exports = { diff --git a/TTXSamples/TTXttyrec/autofilename.c b/TTXSamples/TTXttyrec/autofilename.c new file mode 100644 index 000000000..e74e1c3c8 --- /dev/null +++ b/TTXSamples/TTXttyrec/autofilename.c @@ -0,0 +1,226 @@ +#include +#include +#include + +#include "teraterm.h" + +#include "ttlib_types.h" +#include "codeconv.h" +#include "asprintf.h" + +#include "autofilename.h" + +/** + * 日付および時刻の変換文字列の安全な文字列への置き換え + * strftimeの未サポートフォーマットは無効な文字に置き換える + * + * @param fmt 置き換え対象の文字列 + */ +void ConvertSafeStrFtimeFormat(wchar_t *fmt) { + static const wchar_t *kws=L"aAbBcCdDeFgGhHIjmMnprRStTuUVwWxXyYz%N"; + if (fmt != NULL) { + while (*fmt != L'\0') { + if (*fmt == L'%') { + fmt++; + if (*fmt == L'\0') { + fmt--; + *fmt=L'\0'; + break; + } + if (wcschr(kws, *fmt) == NULL) { + *fmt = L'%'; + } + } + fmt++; + } + } +} + +/** + * ファイル名文字列の置き換え + * 次の文字を置き換える + * &h ホスト名に置換 + * &p TCPポート番号に置換 + * &u ログオン中のユーザ名 + * + * @param pcv + * @param name 置き換える前の文字列(ファイル名) + * @return 置き換えられた文字列 + * 不要になったらfree()すること + */ +wchar_t *GetConvertFilenameW(const TComVar *pcv, const wchar_t *name) +{ + const TTTSet *pts = pcv->ts; + wchar_t *host = NULL; + wchar_t *port = NULL; + wchar_t *user = NULL; + + switch(pcv->PortType) { + case IdTCPIP: + host = ToWcharA(pts->HostName); + aswprintf(&port, L"%d", pts->TCPPort); + break; + case IdSerial: + aswprintf(&host, L"COM%d", pts->ComPort); + port = _wcsdup(L""); + break; + default: + host = _wcsdup(L""); + port = _wcsdup(L""); + break; + } + wchar_t buf[256 + 1]; // 256=UNLEN + DWORD l = _countof(buf); + if (GetUserNameW(buf, &l) != 0) { + user = _wcsdup(buf); + } + else { + user = _wcsdup(L""); + } + + size_t len = wcslen(name) + 1; + const wchar_t *s = name; + + while(*s != '\0') { + if (*s == '&') { + s ++; + if (*s == '\0') { + break; + } + switch (*s) { + case 'h': + len += wcslen(host); + break; + case 'p': + len += wcslen(port); + break; + case 'u': + len += wcslen(user); + break; + default: + ; + break; + } + } + s ++; + } + + wchar_t *res = (wchar_t *)malloc(sizeof(wchar_t) * len); + size_t i = 0; + s = name; + while(*s != '\0') { + if (*s != '&') { + res[i++] = *s++; + continue; + } + s ++; + if (*s == '\0') { + break; + } + switch (*s) { + case 'h': + if (host[0] == L'\0') break; + wcscpy(&res[i], host); + i += wcslen(host); + break; + case 'p': + if (port[0] == L'\0') break; + wcscpy(&res[i], port); + i += wcslen(port); + break; + case 'u': + if (user[0] == L'\0') break; + wcscpy(&res[i], user); + i += wcslen(user); + break; + default: + break; + } + s ++; + } + res[i] = L'\0'; + + free(user); + free(port); + free(host); + return res; +} + +/** + * 自動保存ファイル名取得 + * + * ディレクトリとファイル名の組み合わせ + * filename なしの場合: (path_def, name_def) + * filename は相対パスでない場合:(filename ディレクトリ部, filename ファイル名部) + * filename は相対パスの場合: (path_def, filename) + * + * ディレクトリなしなら GetTermLogDir(pcv->ts) + * + * @param pcv + * @param filename 置き換える前の文字列(ファイル名) + * @param path_def 保存先ディレクトリパスの初期値 + * @param name_def ファイル名の初期値 + * @return 置き換えられた文字列 + * 不要になったらfree()すること + */ +wchar_t *GetAutoFilename(const TComVar *pcv, const wchar_t *filename, const wchar_t *path_def, const wchar_t *name_def) +{ + const TTTSet *pts = pcv->ts; + wchar_t *dir; + wchar_t *fname; + if (filename == NULL || filename[0] == L'\0') { + dir = _wcsdup(path_def); + fname = _wcsdup(name_def); + } + else if (!IsRelativePathW(filename)) { + dir = ExtractDirNameW(filename); + fname = ExtractFileNameW(filename); + } + else { + dir = _wcsdup(path_def); + fname = _wcsdup(filename); + } + + if (fname != NULL && fname[0] != L'\0') { + ConvertSafeStrFtimeFormat(fname); + + time_t time_local; + time(&time_local); + struct tm tm_local; + localtime_s(&tm_local, &time_local); + + size_t len = wcslen(fname) + 32; + wchar_t *buf = (wchar_t *)malloc(len); + size_t r = wcsftime(buf, len, fname, &tm_local); + if (r != 0) { + free(fname); + fname = buf; + } + } + + if (fname != NULL && fname[0] != L'\0') { + wchar_t *buf = GetConvertFilenameW(pcv, fname); + free(fname); + fname = buf; + } + + if (dir == NULL || dir[0] == L'\0') { + free(dir); + dir = GetTermLogDir(pts); + } + if (fname == NULL || fname[0] == L'\0') { + free(fname); + fname = _wcsdup(name_def); + } + else { + wchar_t *replaced = replaceInvalidFileNameCharW(fname, L'_'); + free(fname); + fname = replaced; + } + + wchar_t *full = GetFullPathW(dir, fname); + free(fname); + free(dir); + + return full; +} diff --git a/TTXSamples/TTXttyrec/autofilename.h b/TTXSamples/TTXttyrec/autofilename.h new file mode 100644 index 000000000..d1ca6bc9f --- /dev/null +++ b/TTXSamples/TTXttyrec/autofilename.h @@ -0,0 +1,4 @@ +#pragma once + +void ConvertSafeStrFtimeFormat(wchar_t *fmt); +wchar_t *GetAutoFilename(const TComVar *pcv, const wchar_t *filename, const wchar_t *path_def, const wchar_t *name_def); diff --git a/doc/en/html/about/history.html b/doc/en/html/about/history.html index 004bc9da7..eadf3279c 100644 --- a/doc/en/html/about/history.html +++ b/doc/en/html/about/history.html @@ -6823,6 +6823,17 @@

YYYY.MM.DD (Ver 1.07 not released yet)

Fixed display corruption when playing a tty file after a non-tty file. (issue #1323) +
  • + Added auto recording starts when connected and stops when disconnected. + (issue #1405) +
      +
    • Added RecordAutoStart entry(Start auto recording) to teraterm.ini. Default is off.
    • +
    • Added RecordDefaultPath entry(default recording directory) to teraterm.ini.
    • +
    • Added RecordDefaultName entry(default recording filename) to teraterm.ini.
    • +
    • Default recording filename is able to used strftime format.
    • +
    • Default recording filename is able to include &h(host name/COM port name), &p(port number), or &u(user name).
    • +
    +
  • 2025.08.03 (Ver 1.06)

    diff --git a/doc/en/html/setup/teraterm-ini.html b/doc/en/html/setup/teraterm-ini.html index f04c23b5e..0ba5244eb 100644 --- a/doc/en/html/setup/teraterm-ini.html +++ b/doc/en/html/setup/teraterm-ini.html @@ -2073,7 +2073,7 @@

    TTXKanjiMenu

    -

    TTXttyrec

    +

    ttyrec

    @@ -2082,6 +2082,24 @@

    TTXttyrec

    + + + + + + + + + + + + + + + + + + diff --git a/doc/ja/html/about/history.html b/doc/ja/html/about/history.html index 753a03c51..6a7033c54 100644 --- a/doc/ja/html/about/history.html +++ b/doc/ja/html/about/history.html @@ -6832,6 +6832,17 @@

    YYYY.MM.DD (Ver 1.07 not released yet)

    ttyt@CłȂt@CĐAttyt@CĐƕ\CB (issue #1323) +
  • + ^ljBڑJnŘ^JnAڑؒfŘ^~B + (issue #1405) +
      +
    • teraterm.ini RecordAutoStart GgljB^LݒBftHgoffB
    • +
    • teraterm.ini RecordDefaultPath GgljB^t@Cۑݒ”\B
    • +
    • teraterm.ini RecordDefaultName GgljB^t@Cݒ”\B
    • +
    • ^t@C strftime ̃tH[}bgg悤ɂB
    • +
    • ^t@C &h(ڑ̃zXg/COM|[g)A&p(ڑ̃|[gԍ)A&u([U)܂߂邱Ƃł悤ɂB
    • +
    +
  • 2025.08.03 (Ver 1.06)

    diff --git a/doc/ja/html/setup/teraterm-ini.html b/doc/ja/html/setup/teraterm-ini.html index abd8641bf..b7888d267 100644 --- a/doc/ja/html/setup/teraterm-ini.html +++ b/doc/ja/html/setup/teraterm-ini.html @@ -2073,7 +2073,7 @@

    TTXKanjiMenu

    Default of program Note
    RecordAutoStartoff<-
    RecordDefaultName&h_%Y%m%d_%H%M%S.ttyteraterm.tty
    RecordDefaultPath<-
    RecordStartSize on
    -

    TTXttyrec

    +

    ttyrec

    @@ -2082,6 +2082,24 @@

    TTXttyrec

    + + + + + + + + + + + + + + + + + + diff --git a/installer/release/TERATERM.INI b/installer/release/TERATERM.INI index 7018a5eb9..045129c09 100644 --- a/installer/release/TERATERM.INI +++ b/installer/release/TERATERM.INI @@ -1045,8 +1045,15 @@ Language=Auto UseOneSettingMenu=on AmbiguousCharacterWidthMenu=off -[TTXttyrec] -RecordStartSize=on +[ttyrec] +;RecordStartSize=on + +; Default path to save the record file. +RecordDefaultPath= +; Default Record file name. You can specify strftime format to here. +RecordDefaultName=&h_%Y%m%d_%H%M%S.tty +; Auto start recording with default record file name. (on/off) +RecordAutoStart=off [ttyplay] ; Time format of title
    vOftHg l
    RecordAutoStartoff<-
    RecordDefaultName&h_%Y%m%d_%H%M%S.ttyteraterm.tty
    RecordDefaultPath<-
    RecordStartSize on