/*
 * Copyright (C) 1994-1998 T. Teranishi
 * (C) 2004- TeraTerm Project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/* TERATERM.EXE, DDE routines */
#include "teraterm.h"
#include "tttypes.h"
#include "tttypes_key.h"
#include <stdio.h>
#include <string.h>
#include <ddeml.h>
#include "ttwinman.h"
#include "ttsetup.h"
#include "telnet.h"
#include "ttlib.h"
#include "keyboard.h"
#include "ttdde.h"
#include "ttddecmnd.h"
#include "commlib.h"
#include "sendmem.h"
#include "codeconv.h"
#include "broadcast.h"

#include "filesys.h"
#include "sendmem.h"

#define ServiceName "TERATERM"
#define ItemName "DATA"
#define ItemName2 "PARAM"

#define MaxStrLen (LONG)512

BOOL DDELog = FALSE;		// macro (DDE) が有効かどうかを示す
char TopicName[21] = "";
static HCONV ConvH = 0;
BOOL AdvFlag = FALSE;
BOOL CloseTT = FALSE;

static BOOL DdeCmnd = FALSE;

static DWORD Inst = 0;
static HSZ Service = 0;
static HSZ Topic = 0;
static HSZ Item = 0;
static HSZ Item2 = 0;
static HWND HWndDdeCli = NULL;

static BOOL StartupFlag = FALSE;

// for sync mode
static BOOL SyncMode = FALSE;
static BOOL SyncRecv;
static LONG SyncFreeSpace;

static char ParamFileName[MaxStrLen];
static WORD ParamBinaryFlag;
static WORD ParamXmodemOpt;
static char ParamSecondFileName[MaxStrLen];

static BOOL AutoLogClose = FALSE;

static char *cv_LogBuf;
static int cv_LogPtr;
static int cv_DStart;
static int cv_DCount;

/**
 *	マクロへの送信バッファへ1byteつみこむ
 *		バッファフルの時は古いものから捨てられる
 */
void DDEPut1(BYTE b)
{
	if (!DDELog) {
		return;
	}

	cv_LogBuf[cv_LogPtr] = b;
	cv_LogPtr++;
	if (cv_LogPtr >= InBuffSize)
		cv_LogPtr = cv_LogPtr - InBuffSize;

	if (cv_DCount >= InBuffSize)
	{
		cv_DCount = InBuffSize;
		cv_DStart = cv_LogPtr;
	}
}

static BOOL DDECreateBuf(void)
{
	cv_LogBuf = (char *)malloc(InBuffSize);
	if (cv_LogBuf == NULL) {
		return FALSE;
	}
	cv_LogPtr = 0;
	cv_DStart = 0;
	cv_DCount = 0;
	return TRUE;
}

static void DDEFreeBuf(void)
{
	free(cv_LogBuf);
	cv_LogBuf = NULL;
}

static void BringupMacroWindow(BOOL flash_flag)
{
	HWND hwnd;
	DWORD pid_macro, pid;
	DWORD targetid;
	DWORD currentActiveThreadId;

	currentActiveThreadId = GetWindowThreadProcessId(GetForegroundWindow(), &pid);

	GetWindowThreadProcessId(HWndDdeCli, &pid_macro);
	hwnd = GetTopWindow(NULL);
	while (hwnd) {
		targetid = GetWindowThreadProcessId(hwnd, &pid);
		if (pid == pid_macro) {
			if (flash_flag)
				FlashWindow(hwnd, TRUE);

			SendMessage(hwnd, WM_USER_MSTATBRINGUP, (WPARAM)hwnd, 0);
		}
		hwnd = GetNextWindow(hwnd, GW_HWNDNEXT);
	}

	// マクロウィンドウ本体
	ShowWindow(HWndDdeCli, SW_NORMAL);
	SetForegroundWindow(HWndDdeCli);
	BringWindowToTop(HWndDdeCli);
}


void GetClientHWnd(PCHAR HWndStr)
{
	int i;
	BYTE b;
	LONG HCli;

	HCli = 0;
	i = 0;
	b = HWndStr[0];
	while (b > 0)
	{
		if (b <= 0x39)
			HCli = (HCli << 4) + (b-0x30);
		else
			HCli = (HCli << 4) + (b-0x37);
		i++;
		b = HWndStr[i];
	}
	HWndDdeCli = (HWND)HCli;
}

void Byte2HexStr(BYTE b, LPSTR HexStr)
{
	if (b<0xa0)
		HexStr[0] = 0x30 + (b >> 4);
	else
		HexStr[0] = 0x37 + (b >> 4);
	if ((b & 0x0f) < 0x0a)
		HexStr[1] = 0x30 + (b & 0x0f);
	else
		HexStr[1] = 0x37 + (b & 0x0f);
	HexStr[2] = 0;
}

void SetTopic()
{
	WORD w;

	w = HIWORD(HVTWin);
	Byte2HexStr(HIBYTE(w),&(TopicName[0]));
	Byte2HexStr(LOBYTE(w),&(TopicName[2]));
	w = LOWORD(HVTWin);
	Byte2HexStr(HIBYTE(w),&(TopicName[4]));
	Byte2HexStr(LOBYTE(w),&(TopicName[6]));
}

HDDEDATA WildConnect(HSZ ServiceHsz, HSZ TopicHsz, UINT ClipFmt)
{
	HSZPAIR Pairs[2];
	BOOL Ok;

	Pairs[0].hszSvc  = Service;
	Pairs[0].hszTopic = Topic;
	Pairs[1].hszSvc = NULL;
	Pairs[1].hszTopic = NULL;

	Ok = FALSE;

	if ((ServiceHsz == 0) && (TopicHsz == 0))
		Ok = TRUE;
	else
		if ((TopicHsz == 0) &&
		    (DdeCmpStringHandles(Service, ServiceHsz) == 0))
			Ok = TRUE;
		else
			if ((DdeCmpStringHandles(Topic, TopicHsz) == 0) &&
			    (ServiceHsz == 0))
				Ok = TRUE;

	if (Ok)
		return DdeCreateDataHandle(Inst, (LPBYTE)(&Pairs), sizeof(Pairs),
		                           0, NULL, ClipFmt, 0);
	else
		return 0;
}

static BOOL DDEGet1(LPBYTE b)
{
	if (cv_DCount <= 0) return FALSE;
	*b = ((LPSTR)cv_LogBuf)[cv_DStart];
	cv_DStart++;
	if (cv_DStart>=InBuffSize)
		cv_DStart = cv_DStart-InBuffSize;
	cv_DCount--;
	return TRUE;
}

static LONG DDEGetDataLen()
{
	BYTE b;
	LONG Len;
	int Start, Count;

	Len = cv_DCount;
	Start = cv_DStart;
	Count = cv_DCount;
	while (Count>0)
	{
		b = ((LPSTR)cv_LogBuf)[Start];
		if ((b==0x00) || (b==0x01)) Len++;
		Start++;
		if (Start>=InBuffSize) Start = Start-InBuffSize;
		Count--;
	}

	return Len;
}

/**
 *	送信バッファに残っているデータ数取得
 */
int DDEGetCount(void)
{
	return cv_DCount;
}

static HDDEDATA AcceptRequest(HSZ ItemHSz)
{
	BYTE b;
	HDDEDATA DH;
	LPSTR DP;
	int i;
	LONG Len;

	if ((! DDELog) || (ConvH==0)) return 0;

	if (DdeCmpStringHandles(ItemHSz, Item2) == 0) // item "PARAM"
		DH = DdeCreateDataHandle(Inst,ParamFileName,sizeof(ParamFileName),0,
		                         Item2,CF_OEMTEXT,0);
	else if (DdeCmpStringHandles(ItemHSz, Item) == 0) // item "DATA"
	{
		Len = DDEGetDataLen();
		if ((SyncMode) &&
		    (SyncFreeSpace<Len))
			Len = SyncFreeSpace;

		DH = DdeCreateDataHandle(Inst,NULL,Len+2,0,
		                         Item,CF_OEMTEXT,0);
		DP = DdeAccessData(DH,NULL);
		if (DP != NULL)
		{
			i = 0;
			while (i < Len)
			{
				if (DDEGet1(&b)) {
					if ((b==0x00) || (b==0x01))
					{
						DP[i] = 0x01;
						DP[i+1] = b + 1;
						i = i + 2;
					}
					else {
						DP[i] = b;
						i++;
					}
				}
				else
					Len = 0;
			}
			DP[i] = 0;
			DdeUnaccessData(DH);
		}
	}
	else
		return 0;

	return DH;
}

static HDDEDATA AcceptPoke(HSZ ItemHSz, UINT ClipFmt,
						   HDDEDATA Data)
{
	LPSTR DataPtr;
	DWORD DataSize;
	HDDEDATA result;

	// 連続してXTYP_POKEがクライアント（マクロ）から送られてくると、サーバ（本体）側がまだ
	// コマンドの貼り付けを行っていない場合、TalkStatusは IdTalkCB になので、DDE_FNOTPROCESSEDを
	// 返すことがある。DDE_FBUSYに変更。
	// (2006.11.6 yutaka)
	if (TalkStatus != IdTalkKeyb)
		return (HDDEDATA)DDE_FBUSY;

	if (ConvH==0) return DDE_FNOTPROCESSED;

	if ((ClipFmt!=CF_TEXT) && (ClipFmt!=CF_OEMTEXT)) return DDE_FNOTPROCESSED;

	if (DdeCmpStringHandles(ItemHSz, Item) != 0) return DDE_FNOTPROCESSED;

	result = (HDDEDATA)DDE_FNOTPROCESSED;
	DataPtr = DdeAccessData(Data,&DataSize);
	if (DataPtr != NULL) {
		wchar_t *strW = ToWcharU8(DataPtr);
		if (strW != NULL) {
			SendMem *sm = SendMemTextW(strW, 0);
			if (sm != NULL) {
				SendMemInitEcho(sm, FALSE);
				SendMemInitDelay(sm, SENDMEM_DELAYTYPE_PER_LINE, 10, 0);
				SendMemStart(sm);
				result = (HDDEDATA)DDE_FACK;
			}
		}
	}
	DdeUnaccessData(Data);
	return result;
}

static WORD HexStr2Word(PCHAR Str)
{
	int i;
	BYTE b;
	WORD w = 0;

	for (i=0; i<=3; i++)
	{
		b = Str[i];
		if (b <= 0x39)
			w = (w << 4) + (b-0x30);
		else
		w = (w << 4) + (b-0x37);
	}
	return w;
}

static HDDEDATA AcceptExecute(HSZ TopicHSz, HDDEDATA Data)
{
	char Command[MaxStrLen + 1];
	int i;
	wchar_t *ParamFileNameW;
	BOOL r;

	memset(Command, 0, sizeof(Command));

	if ((ConvH==0) ||
		(DdeCmpStringHandles(TopicHSz, Topic) != 0) ||
		(DdeGetData(Data,Command,sizeof(Command),0) == 0))
		return DDE_FNOTPROCESSED;

	switch (Command[0]) {
	case CmdSetHWnd:
		GetClientHWnd(&Command[1]);
		if (cv.Ready)
			SetDdeComReady(1);
		break;
	case CmdSetFile:
		strncpy_s(ParamFileName, sizeof(ParamFileName),&(Command[1]), _TRUNCATE);
		break;
	case CmdSetBinary:
		ParamBinaryFlag = Command[1] & 1;
		break;
	case CmdSetLogOpt:
		ts.LogBinary        = Command[LogOptBinary] - 0x30;
		ts.Append           = Command[LogOptAppend] - 0x30;
		ts.LogTypePlainText = Command[LogOptPlainText] - 0x30;
		ts.LogTimestamp     = Command[LogOptTimestamp] - 0x30;
		ts.LogHideDialog    = Command[LogOptHideDialog] - 0x30;
		ts.LogAllBuffIncludedInFirst = Command[LogOptIncScrBuff] - 0x30;
		ts.LogTimestampType = Command[LogOptTimestampType] - 0x30;
		break;
	case CmdSetXmodemOpt:
		ParamXmodemOpt = Command[1] & 3;
		if (ParamXmodemOpt==0) ParamXmodemOpt = 1;
		break;
	case CmdSetSync:
		if (sscanf(&(Command[1]),"%lu",&SyncFreeSpace)!=1)
			SyncFreeSpace = 0;
		SyncMode = (SyncFreeSpace>0);
		SyncRecv = TRUE;
		break;
	case CmdBPlusRecv:
		if (BPStartReceive(TRUE, FALSE)) {
			DdeCmnd = TRUE;
		}
		else
			return DDE_FNOTPROCESSED;
		break;
	case CmdBPlusSend:
		ParamFileNameW = ToWcharU8(ParamFileName);
		r = BPStartSend(ParamFileNameW);
		free(ParamFileNameW);
		if (r) {
			DdeCmnd = TRUE;
		}
		else
			return DDE_FNOTPROCESSED;
		break;
	case CmdChangeDir:
		strncpy_s(ts.FileDir, sizeof(ts.FileDir),ParamFileName, _TRUNCATE);
		break;
	case CmdClearScreen:
		switch (ParamFileName[0]) {
		case '0':
			PostMessage(HVTWin,WM_USER_ACCELCOMMAND,IdCmdEditCLS,0);
			break;
		case '1':
			PostMessage(HVTWin,WM_USER_ACCELCOMMAND,IdCmdEditCLB,0);
			break;
		case '2':
			PostMessage(HTEKWin,WM_USER_ACCELCOMMAND,IdCmdEditCLS,0);
			break;
		}
		break;
	case CmdCloseWin:
		CloseTT = TRUE;
		break;
	case CmdConnect:
		if (cv.Open) {
			if (cv.Ready)
				SetDdeComReady(1);
			break;
		}
		{
		char Temp[MaxStrLen + 2];
		strncpy_s(Temp, sizeof(Temp),"a ", _TRUNCATE); // dummy exe name
		strncat_s(Temp,sizeof(Temp),ParamFileName,_TRUNCATE);
		if (LoadTTSET())
			(*ParseParam)(Temp, &ts, NULL);
		FreeTTSET();
		cv.NoMsg = 1; /* suppress error messages */
		PostMessage(HVTWin,WM_USER_COMMSTART,0,0);
		}
		break;
	case CmdDisconnect:
		if (ParamFileName[0] == '0') {
			PostMessage(HVTWin,WM_USER_ACCELCOMMAND,IdCmdDisconnect,0);
		}
		else {
			PostMessage(HVTWin,WM_USER_ACCELCOMMAND,IdCmdDisconnect,1);
		}
		break;
	case CmdEnableKeyb:
		KeybEnabled = (ParamBinaryFlag!=0);
		break;
	case CmdGetTitle:
		// title is transferred later by XTYP_REQUEST
		strncpy_s(ParamFileName, sizeof(ParamFileName),ts.Title, _TRUNCATE);
		break;
	case CmdInit: // initialization signal from TTMACRO
		if (StartupFlag) // in case of startup macro
		{ // TTMACRO is waiting for connecting to the host
			// シリアル接続で自動接続が無効の場合は、接続ダイアログを出さない (2006.9.15 maya)
			if (!((ts.PortType==IdSerial) && (ts.ComAutoConnect == FALSE)) &&
				((ts.PortType==IdSerial) || (ts.HostName[0]!=0)))
			{
				cv.NoMsg = 1;
				// start connecting
				PostMessage(HVTWin,WM_USER_COMMSTART,0,0);
			}
			else  // notify TTMACRO that I can not connect
				SetDdeComReady(0);
			StartupFlag = FALSE;
		}
		break;
	case CmdKmtFinish:
		if (KermitFinish(TRUE)) {
			DdeCmnd = TRUE;
		}
		else {
			return DDE_FNOTPROCESSED;
		}
		break;
	case CmdKmtRecv:
		if (KermitStartRecive(TRUE)) {
			DdeCmnd = TRUE;
		}
		else
			return DDE_FNOTPROCESSED;
		break;
	case CmdKmtGet:
		ParamFileNameW = ToWcharU8(ParamFileName);
		r = KermitGet(ParamFileNameW);
		free(ParamFileNameW);
		if (r) {
			DdeCmnd = TRUE;
		}
		else
			return DDE_FNOTPROCESSED;
		break;
	case CmdKmtSend:
		ParamFileNameW = ToWcharU8(ParamFileName);
		r = KermitStartSend(ParamFileNameW);
		free(ParamFileNameW);
		if (r) {
			DdeCmnd = TRUE;
		}
		else
			return DDE_FNOTPROCESSED;
		break;
	case CmdLoadKeyMap:
		strncpy_s(ts.KeyCnfFN, sizeof(ts.KeyCnfFN),ParamFileName, _TRUNCATE);
		PostMessage(HVTWin,WM_USER_ACCELCOMMAND,IdCmdLoadKeyMap,0);
		break;

	case CmdLogRotate: {
		char *p = ParamFileName;
		int s;

		if (strncmp(p, "size", 4) == 0) {
			s = atoi(&p[5]);
			FLogRotateSize(s);

		} else if (strncmp(p, "rotate", 6) == 0) {
			s = atoi(&p[7]);
			FLogRotateRotate(s);

		} else if (strncmp(p, "halt", 4) == 0) {
			FLogRotateHalt();
		}
		break;
	}

	case CmdLogAutoClose:
		AutoLogClose = (ParamBinaryFlag!=0);
		break;

	case CmdLogClose:
		FLogClose();
		break;
	case CmdLogOpen:
		if (FLogIsOpend()) {
			return DDE_FNOTPROCESSED;
		}
		else {
			wchar_t *ParamFileNameW = ToWcharU8(ParamFileName);
			wchar_t *log_filenameW = FLogGetLogFilename(ParamFileNameW);
			BOOL ret = FLogOpen(log_filenameW, LOG_UTF8, FALSE);
			free(log_filenameW);
			free(ParamFileNameW);
			strncpy_s(ParamFileName, sizeof(ParamFileName), ret ? "1" : "0", _TRUNCATE);
		}
		break;
	case CmdLogPause:
		FLogPause(TRUE);
		break;
	case CmdLogStart:
		FLogPause(FALSE);
		break;
	case CmdLogWrite: {
		wchar_t *ParamFileNameW = ToWcharU8(ParamFileName);
		FLogWriteStr(ParamFileNameW);
		free(ParamFileNameW);
		break;
	}
	case CmdQVRecv:
		if (QVStartReceive(TRUE)) {
			DdeCmnd = TRUE;
		}
		else
			return DDE_FNOTPROCESSED;
		break;
	case CmdQVSend:
		ParamFileNameW = ToWcharU8(ParamFileName);
		r = QVStartSend(ParamFileNameW);
		free(ParamFileNameW);
		if (r) {
			DdeCmnd = TRUE;
		}
		else
			return DDE_FNOTPROCESSED;
		break;
	case CmdRestoreSetup:
		strncpy_s(ts.SetupFName, sizeof(ts.SetupFName),ParamFileName, _TRUNCATE);
		PostMessage(HVTWin,WM_USER_ACCELCOMMAND,IdCmdRestoreSetup,0);
		break;
	case CmdSendBreak:
		PostMessage(HVTWin,WM_USER_ACCELCOMMAND,IdBreak,0);
		break;
	case CmdSendFile: {
		wchar_t *ParamFileNameW = ToWcharU8(ParamFileName);
#if 0
		// Tera Term 4 と同じ方法で送信
		BOOL r = FileSendStart(ParamFileNameW, ParamBinaryFlag);
#else
		// 5 で追加した方法で送信
		BOOL r = SendMemSendFile(ParamFileNameW, ParamBinaryFlag, 0, 0, 0);
#endif
		free(ParamFileNameW);
		if (r) {
			DdeCmnd = TRUE;
		}
		else
			return DDE_FNOTPROCESSED;
		break;
	}
	case CmdSendKCode: {
		WORD w = HexStr2Word(ParamFileName);
		WORD c = HexStr2Word(&ParamFileName[4]);
		PostMessage(HVTWin,WM_USER_KEYCODE,w,(LPARAM)c);
		break;
	}
	case CmdSetEcho:
		ts.LocalEcho = ParamBinaryFlag;
		if (cv.Ready && cv.TelFlag && (ts.TelEcho>0))
			TelChangeEcho();
		break;
	case CmdSetDebug:
		DebugFlag = (Command[1]-'0')%DEBUG_FLAG_MAXD;
		break;
	case CmdSetTitle:
		strncpy_s(ts.Title, sizeof(ts.Title),ParamFileName, _TRUNCATE);
		if (ts.AcceptTitleChangeRequest == IdTitleChangeRequestOverwrite) {
			cv.TitleRemote[0] = '\0';
		}
		ChangeTitle();
		break;
	case CmdShowTT:
		switch (ParamFileName[0]) {
		case '-': ShowWindow(HVTWin,SW_HIDE); break;
		case '0': ShowWindow(HVTWin,SW_MINIMIZE); break;
		case '1': ShowWindow(HVTWin,SW_RESTORE); break;
		case '2': ShowWindow(HTEKWin,SW_HIDE); break;
		case '3': ShowWindow(HTEKWin,SW_MINIMIZE); break;
		case '4':
			PostMessage(HVTWin,WM_USER_ACCELCOMMAND,IdCmdCtrlOpenTEK,0);
			break;
		case '5':
			PostMessage(HVTWin,WM_USER_ACCELCOMMAND,IdCmdCtrlCloseTEK,0);
			break;
		case '6': //steven add
			FLogWindow(SW_HIDE);
			break;
		case '7': //steven add
			FLogWindow(SW_MINIMIZE);
			break;
		case '8': //steven add
			FLogWindow(SW_RESTORE);
			break;
		}
		break;
	case CmdXmodemRecv:
		ParamFileNameW = ToWcharU8(ParamFileName);
		r = XMODEMStartSend(ParamFileNameW, ParamXmodemOpt);
		free(ParamFileNameW);
		if (r) {
			DdeCmnd = TRUE;
		}
		else
			return DDE_FNOTPROCESSED;
		break;
	case CmdXmodemSend:
		ParamFileNameW = ToWcharU8(ParamFileName);
		r = XMODEMStartReceive(ParamFileNameW, ParamBinaryFlag, ParamXmodemOpt);
		free(ParamFileNameW);
		if (r) {
			DdeCmnd = TRUE;
		}
		else
			return DDE_FNOTPROCESSED;
		break;
	case CmdZmodemRecv:
		if (ZMODEMStartReceive(TRUE, FALSE)) {
			DdeCmnd = TRUE;
		}
		else
			return DDE_FNOTPROCESSED;
		break;
	case CmdZmodemSend:
		ParamFileNameW = ToWcharU8(ParamFileName);
		r = ZMODEMStartSend(ParamFileNameW, ParamBinaryFlag, FALSE);
		free(ParamFileNameW);
		if (r) {
			DdeCmnd = TRUE;
		}
		else
			return DDE_FNOTPROCESSED;
		break;

	case CmdYmodemRecv:
		if (YMODEMStartReceive(TRUE))
		{
			DdeCmnd = TRUE;
		}
		else
			return DDE_FNOTPROCESSED;
		break;
	case CmdYmodemSend:
		ParamFileNameW = ToWcharU8(ParamFileName);
		r = YMODEMStartSend(ParamFileNameW);
		if (r) {
			DdeCmnd = TRUE;
		}
		else
			return DDE_FNOTPROCESSED;
		break;

		// add 'callmenu' (2007.11.18 maya)
	case CmdCallMenu:
		i = atoi(ParamFileName);
		if (i >= 51110 && i <= 51990) {
			PostMessage(HTEKWin,WM_COMMAND,MAKELONG(i,0),0);
		}
		else {
			PostMessage(HVTWin,WM_COMMAND,MAKELONG(i,0),0);
		}
		break;

	case CmdSetSecondFile:  // add 'scpsend' (2008.1.3 yutaka)
		memset(ParamSecondFileName, 0, sizeof(ParamSecondFileName));
		strncpy_s(ParamSecondFileName, sizeof(ParamSecondFileName),&(Command[1]), _TRUNCATE);
		break;

	case CmdScpSend:  // add 'scpsend' (2008.1.1 yutaka)
		{
		typedef int (CALLBACK *PSSH_start_scp)(char *, char *);
		static PSSH_start_scp func = NULL;
		static HMODULE h = NULL;
		char msg[128];

		if (func == NULL) {
			if ( ((h = GetModuleHandle("ttxssh.dll")) == NULL) ) {
				_snprintf_s(msg, sizeof(msg), _TRUNCATE, "GetModuleHandle(\"ttxssh.dll\")) %d", GetLastError());
				goto scp_send_error;
			}
			func = (PSSH_start_scp)GetProcAddress(h, "TTXScpSendfile");
			if (func == NULL) {
				_snprintf_s(msg, sizeof(msg), _TRUNCATE, "GetProcAddress(\"TTXScpSendfile\")) %d", GetLastError());
				goto scp_send_error;
			}
		}

		if (func != NULL) {
			// DdeCmnd = TRUE;
			func(ParamFileName, ParamSecondFileName);
			break;
		}

scp_send_error:
		MessageBox(NULL, msg, "Tera Term: scpsend command error", MB_OK | MB_ICONERROR);
		return DDE_FNOTPROCESSED;
		}
		break;

	case CmdScpRcv:
		{
		typedef int (CALLBACK *PSSH_start_scp)(char *, char *);
		static PSSH_start_scp func = NULL;
		static HMODULE h = NULL;
		char msg[128];

		if (func == NULL) {
			if ( ((h = GetModuleHandle("ttxssh.dll")) == NULL) ) {
				_snprintf_s(msg, sizeof(msg), _TRUNCATE, "GetModuleHandle(\"ttxssh.dll\")) %d", GetLastError());
				goto scp_rcv_error;
			}
			func = (PSSH_start_scp)GetProcAddress(h, "TTXScpReceivefile");
			if (func == NULL) {
				_snprintf_s(msg, sizeof(msg), _TRUNCATE, "GetProcAddress(\"TTXScpReceivefile\")) %d", GetLastError());
				goto scp_rcv_error;
			}
		}

		if (func != NULL) {
			// DdeCmnd = TRUE;
			func(ParamFileName, ParamSecondFileName);
			break;
		}

scp_rcv_error:
		MessageBox(NULL, msg, "Tera Term: scpsend command error", MB_OK | MB_ICONERROR);
		return DDE_FNOTPROCESSED;
		}
		break;

	case CmdSetBaud:  // add 'setbaud' (2008.2.13 steven patch)
		{
		int val;

		//OutputDebugPrintf("CmdSetBaud entered\n");

		if (!cv.Open || cv.PortType != IdSerial)
			return DDE_FNOTPROCESSED;

		val = atoi(ParamFileName);
		//OutputDebugPrintf("CmdSetBaud: %d (%d)\n", val, ts.Baud);
		if (val > 0) {
			ts.Baud = val;
			CommResetSerial(&ts,&cv,FALSE);   // reset serial port
			PostMessage(HVTWin,WM_USER_CHANGETITLE,0,0); // refresh title bar
		}
		}
		break;

	case CmdSetFlowCtrl:
		{
		int val;

		if (!cv.Open || cv.PortType != IdSerial)
			return DDE_FNOTPROCESSED;

		val = atoi(ParamFileName);
		switch(val) {
			case 1: // Xon/Xoff
			case 2: // RTS/CTS
			case 3: // none
			case 4: // DSR/DTR
				ts.Flow = val;
				break;
		}
		break;
		}

	case CmdSetRts:  // add 'setrts' (2008.3.12 maya)
		{
		int val, ret;

		if (!cv.Open || cv.PortType != IdSerial || ts.Flow != IdFlowNone)
			return DDE_FNOTPROCESSED;

		val = atoi(ParamFileName);
		if (val == 0) {
			ret = EscapeCommFunction(cv.ComID, CLRRTS);
		}
		else {
			ret = EscapeCommFunction(cv.ComID, SETRTS);
		}
		}
		break;

	case CmdSetDtr:  // add 'setdtr' (2008.3.12 maya)
		{
		int val, ret;

		if (!cv.Open || cv.PortType != IdSerial || ts.Flow != IdFlowNone)
			return DDE_FNOTPROCESSED;

		val = atoi(ParamFileName);
		if (val == 0) {
			ret = EscapeCommFunction(cv.ComID, CLRDTR);
		}
		else {
			ret = EscapeCommFunction(cv.ComID, SETDTR);
		}
		}
		break;

	case CmdGetModemStatus: // add 'getmodemstatus' (2015.1.8 yutaka)
		{
		DWORD val, n;

		if (!cv.Open || cv.PortType != IdSerial)
			return DDE_FNOTPROCESSED;

		if (GetCommModemStatus(cv.ComID, &val) == 0) // error
			return DDE_FNOTPROCESSED;
		//val = MS_CTS_ON | MS_DSR_ON | MS_RING_ON | MS_RLSD_ON;
		n = 0;
		if (val & MS_CTS_ON)
			n |= 0x01;
		if (val & MS_DSR_ON)
			n |= 0x02;
		if (val & MS_RING_ON)
			n |= 0x04;
		if (val & MS_RLSD_ON)
			n |= 0x08;

		_snprintf_s(ParamFileName, sizeof(ParamFileName), _TRUNCATE, "%d", n);
		}
		break;

	case CmdGetHostname:  // add 'gethostname' (2008.12.15 maya)
		if (cv.Open) {
			if (cv.PortType == IdTCPIP) {
				strncpy_s(ParamFileName, sizeof(ParamFileName),ts.HostName, _TRUNCATE);
			}
			else if (cv.PortType == IdSerial) {
				_snprintf_s(ParamFileName, sizeof(ParamFileName), _TRUNCATE, "COM%d", ts.ComPort);
			}
		}
		break;

	case CmdSendBroadcast: { // 'sendbroadcast'
		wchar_t *strW = ToWcharU8(ParamFileName);
		if (strW != NULL) {
			SendBroadcastMessage(HVTWin, HVTWin, strW);
			free(strW);
		}
		break;
	}

	case CmdSendMulticast: {
		// 'sendmulticast'
		wchar_t *ParamFileNameW = ToWcharU8(ParamFileName);
		wchar_t *ParamSecondFileNameW = ToWcharU8(ParamSecondFileName);
		if (ParamFileNameW != NULL && ParamSecondFileNameW != NULL) {
			SendMulticastMessage(HVTWin, HVTWin, ParamFileNameW, ParamSecondFileNameW);
		}
		free(ParamFileNameW);
		free(ParamSecondFileNameW);
		break;
	}

	case CmdSetMulticastName: {
		// 'setmulticastname'
		wchar_t *ParamFileNameW = ToWcharU8(ParamFileName);
		if (ParamFileNameW != NULL) {
			SetMulticastName(ParamFileNameW);
			free(ParamFileNameW);
		}
		break;
	}

	case CmdDispStr: {
		wchar_t *strW = ToWcharU8(ParamFileName);
		if (strW != NULL) {
			SendMem *sm = SendMemTextW(strW, 0);
			if (sm != NULL) {
				SendMemInitEcho(sm, TRUE);
				SendMemInitSend(sm, FALSE);
				SendMemStart(sm);
			}
		}
		break;
	}

	case CmdLogInfo:
		FLogInfo(ParamFileName, sizeof(ParamFileName) - 1);
		break;

	default:
		return DDE_FNOTPROCESSED;
	}
	return (HDDEDATA)DDE_FACK;
}

static HDDEDATA CALLBACK DdeCallbackProc(UINT CallType, UINT Fmt, HCONV Conv,
										 HSZ HSz1, HSZ HSz2, HDDEDATA Data,
										 ULONG_PTR Data1, ULONG_PTR Data2)
{
	HDDEDATA Result;

	if (Inst==0) return 0;
	Result = 0;

	switch (CallType) {
		case XTYP_WILDCONNECT:
			Result = WildConnect(HSz2, HSz1, Fmt);
			break;
		case XTYP_CONNECT:
			if (Conv == 0)
			{
				if ((DdeCmpStringHandles(Topic, HSz1) == 0) &&
				    (DdeCmpStringHandles(Service, HSz2) == 0))
				{
					if (cv.Ready)
						SetDdeComReady(1);
					Result = (HDDEDATA)TRUE;
				}
			}
			break;
		case XTYP_CONNECT_CONFIRM:
			ConvH = Conv;
			break;
		case XTYP_ADVREQ:
		case XTYP_REQUEST:
			Result = AcceptRequest(HSz2);
			break;

	// クライアント(ttpmacro.exe)からサーバ(ttermpro.exe)へデータが送られてくると、
	// このメッセージハンドラへ飛んでくる。
	// 処理したら DDE_FACK、ビジーの場合は DDE_FBUSY 、無視する場合は DDE_FNOTPROCESSED を
	// クライアントへ返す必要があり、break文が抜けていたので追加した。
	// (2006.11.6 yutaka)
		case XTYP_POKE:
			Result = AcceptPoke(HSz2, Fmt, Data);
			break;

		case XTYP_ADVSTART:
			if ((ConvH!=0) &&
			    (DdeCmpStringHandles(Topic, HSz1) == 0) &&
			    (DdeCmpStringHandles(Item, HSz2) == 0) &&
			    ! AdvFlag)
			{
				AdvFlag = TRUE;
				Result = (HDDEDATA)TRUE;
			}
			break;
		case XTYP_ADVSTOP:
			if ((ConvH!=0) &&
			    (DdeCmpStringHandles(Topic, HSz1) == 0) &&
			    (DdeCmpStringHandles(Item, HSz2) == 0) &&
			    AdvFlag)
			{
				AdvFlag = FALSE;
				Result = (HDDEDATA)TRUE;
			}
			break;
		case XTYP_DISCONNECT:
			// マクロ終了時、ログ採取を自動的に停止する。(2013.6.24 yutaka)
			if (AutoLogClose) {
				FLogClose();
				AutoLogClose = FALSE;
			}
			ConvH = 0;
			PostMessage(HVTWin,WM_USER_DDEEND,0,0);
			break;
		case XTYP_EXECUTE:
			Result = AcceptExecute(HSz1, Data);
	}  /* switch (CallType) */

	return Result;
}

BOOL InitDDE()
{
	BOOL Ok;

	if (ConvH != 0) return FALSE;

	Ok = TRUE;

	if (DdeInitialize(&Inst,DdeCallbackProc,0,0) == DMLERR_NO_ERROR)
	{
		Service= DdeCreateStringHandle(Inst, ServiceName, CP_WINANSI);
		Topic  = DdeCreateStringHandle(Inst, TopicName, CP_WINANSI);
		Item   = DdeCreateStringHandle(Inst, ItemName, CP_WINANSI);
		Item2  = DdeCreateStringHandle(Inst, ItemName2, CP_WINANSI);

		Ok = DdeNameService(Inst, Service, 0, DNS_REGISTER) != 0;
	}
	else
		Ok = FALSE;

	SyncMode = FALSE;
	CloseTT = FALSE;
	StartupFlag = FALSE;
	DDELog = FALSE;

	if (Ok)
	{
		Ok = DDECreateBuf();
		if (Ok) DDELog = TRUE;
	}

	if (! Ok) EndDDE();
	return Ok;
}

void SendDDEReady()
{
	GetClientHWnd(TopicName);
	PostMessage(HWndDdeCli,WM_USER_DDEREADY,0,0);
}

void EndDDE()
{
	DWORD Temp;

	if (ConvH != 0)
		DdeDisconnect(ConvH);
	ConvH = 0;
	SyncMode = FALSE;
	StartupFlag = FALSE;

	Temp = Inst;
	if (Inst != 0)
	{
		Inst = 0;
		DdeNameService(Temp, Service, 0, DNS_UNREGISTER);
		if (Service != 0)
			DdeFreeStringHandle(Temp, Service);
		Service = 0;
		if (Topic != 0)
			DdeFreeStringHandle(Temp, Topic);
		Topic = 0;
		if (Item != 0)
			DdeFreeStringHandle(Temp, Item);
		Item = 0;
		if (Item2 != 0)
			DdeFreeStringHandle(Temp, Item2);
		Item2 = 0;

		DdeUninitialize(Temp);
	}
	TopicName[0] = 0;

	if (HWndDdeCli!=NULL)
		PostMessage(HWndDdeCli,WM_USER_DDECMNDEND,0,0);
	HWndDdeCli = NULL;
	AdvFlag = FALSE;

	DDELog = FALSE;
	DDEFreeBuf();
	cv.NoMsg = 0;
}

void DDEAdv()
{
	if ((ConvH==0) ||
	    (! AdvFlag) ||
	    (DDEGetCount() == 0))
		return;

	if ((! SyncMode) ||
	    SyncMode && SyncRecv)
	{
		if (SyncFreeSpace<10)
			SyncFreeSpace=0;
		else
			SyncFreeSpace=SyncFreeSpace-10;
		DdePostAdvise(Inst,Topic,Item);
		SyncRecv = FALSE;
	}
}

void EndDdeCmnd(int Result)
{
	if ((ConvH==0) || (HWndDdeCli==NULL) || (! DdeCmnd)) return;
	PostMessage(HWndDdeCli,WM_USER_DDECMNDEND,(WPARAM)Result,0);
	DdeCmnd = FALSE;
}

void SetDdeComReady(WORD Ready)
{
	if (HWndDdeCli==NULL) return;
	PostMessage(HWndDdeCli,WM_USER_DDECOMREADY,Ready,0);
}

void RunMacro(PCHAR FName, BOOL Startup)
//  FName: macro filename
//  Startup: TRUE in case of startup macro execution.
//		  In this case, the connection to the host will
//		  made after the link to TT(P)MACRO is established.
{
	PROCESS_INFORMATION pi;
	char Cmnd[MAX_PATH+36]; // "TTPMACRO /D="(12) + TopicName(20) + " "(1) + MAX_PATH + " /S"(3)
	STARTUPINFO si;
	DWORD pri = NORMAL_PRIORITY_CLASS;

	// Control menuからのマクロ呼び出しで、すでにマクロ起動中の場合、
	// 該当する"ttpmacro"をフラッシュする。
	// (2010.4.2 yutaka, maya)
	if ((FName == NULL && Startup == FALSE) && ConvH != 0) {
		BringupMacroWindow(TRUE);
		return;
	}

	SetTopic();
	if (! InitDDE()) return;
	strncpy_s(Cmnd, sizeof(Cmnd),"TTPMACRO /D=", _TRUNCATE);
	strncat_s(Cmnd,sizeof(Cmnd),TopicName,_TRUNCATE);
	if (FName!=NULL)
	{
		size_t i;
		strncat_s(Cmnd,sizeof(Cmnd)," ",_TRUNCATE);
		i = strlen(Cmnd);
		strncat_s(Cmnd,sizeof(Cmnd),FName,_TRUNCATE);
		QuoteFName(&Cmnd[i]);
	}

	StartupFlag = Startup;
	if (Startup)
		strncat_s(Cmnd,sizeof(Cmnd)," /S",_TRUNCATE); // "startup" flag

#if 0
	if (WinExec(Cmnd,SW_MINIMIZE) < 32)
		EndDDE();
#else

	// ログ採取中も下げないことにする。(2005.8.14 yutaka)
#if 0
	// Tera Term本体でログ採取中にマクロを実行すると、マクロの動作が停止することが
	// あるため、プロセスの優先度を1つ下げて実行させる。(2004/9/5 yutaka)
	// ログ採取中のみに下げる。(2004/11/28 yutaka)
	if (FileLog || BinLog) {
		pri = BELOW_NORMAL_PRIORITY_CLASS;
	}
	// 暫定処置として、常に下げることにする。(2005/5/15 yutaka)
	// マクロによるtelnet自動ログインが失敗することがあるので、下げないことにする。(2005/5/23 yutaka)
	pri = BELOW_NORMAL_PRIORITY_CLASS;
#endif

	ZeroMemory(&si, sizeof(si));
	ZeroMemory(&pi, sizeof(pi));
	GetStartupInfo(&si);
	si.wShowWindow = SW_MINIMIZE;

	if (CreateProcess(
		NULL,
		Cmnd,
		NULL,
		NULL,
		FALSE,
		pri,
		NULL, NULL,
		&si, &pi) == 0) {
			EndDDE();
	}
#endif
}
