csharp
复制代码
using EA;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
namespace EA_CodeEngineer_64.Template.TabUserControl
{
public partial class UserControlCmdHandle : UserControl
{
// Windows API 函数声明
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
[DllImport("user32.dll", SetLastError = true)]
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll", SetLastError = true)]
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
private static extern IntPtr SetFocus(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern IntPtr SetActiveWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool BringWindowToTop(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern void SwitchToThisWindow(IntPtr hWnd, bool fAltTab);
[DllImport("kernel32.dll")]
private static extern IntPtr GetStdHandle(int nStdHandle);
[DllImport("kernel32.dll")]
private static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode);
[DllImport("kernel32.dll")]
private static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode);
[DllImport("user32.dll")]
private static extern IntPtr PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll")]
private static extern bool AttachConsole(uint dwProcessId);
[DllImport("kernel32.dll")]
private static extern bool FreeConsole();
private const uint WM_KEYDOWN = 0x0100;
private const uint WM_KEYUP = 0x0101;
private const uint WM_CHAR = 0x0102;
private const byte VK_DOWN = 0x28;
private const byte VK_RETURN = 0x0D;
private const int STD_INPUT_HANDLE = -10;
private const uint ENABLE_EXTENDED_FLAGS = 0x0080;
private const uint ENABLE_QUICK_EDIT_MODE = 0x0040;
private const int SW_SHOW = 5;
// 窗口样式常量
private const int GWL_STYLE = -16;
private const int WS_CHILD = 0x40000000;
private const int WS_VISIBLE = 0x10000000;
private const int WS_CAPTION = 0x00C00000;
private const int WS_BORDER = 0x00800000;
private Process cmdProcess;
private Panel cmdPanel;
private bool _isEmbedded = false;
private bool _isProcessStarted = false;
/// <summary>
/// 工作目录(CMD 启动后的初始目录)。如果不设置,将使用 Model.GetCodePath()
/// </summary>
public string WorkingDirectory { get; set; }
/// <summary>
/// 是否自动执行 claude 并发送按键(需要 claude 已安装)
/// </summary>
public bool AutoExecute { get; set; } = true;
public UserControlCmdHandle()
{
InitializeComponent();
SetStyle(ControlStyles.Selectable, true);
this.TabStop = true;
}
private void UserControlCmdHandle_Load(object sender, EventArgs e)
{
InitializeEmbedding();
// 延迟一下再选择控件,确保嵌入完成后再尝试激活
this.BeginInvoke(new Action(() => this.Select()));
}
private void InitializeEmbedding()
{
cmdPanel = new Panel
{
Dock = DockStyle.Fill,
BackColor = System.Drawing.Color.Black
};
// 让 Panel 不处理鼠标点击(直接传递给子窗口)
cmdPanel.MouseDown += (s, e) => ActivateCmdWindow();
this.Controls.Add(cmdPanel);
Thread thread = new Thread(StartCmdAndEmbed);
thread.IsBackground = true;
thread.Start();
}
protected override void WndProc(ref Message m)
{
const int WM_MOUSEACTIVATE = 0x0021;
if (m.Msg == WM_MOUSEACTIVATE)
{
// 当鼠标点击 UserControl 时,立即激活 CMD 窗口
ActivateCmdWindow();
// 返回 MA_ACTIVATE 让系统继续处理
m.Result = (IntPtr)1; // MA_ACTIVATE
return;
}
base.WndProc(ref m);
}
public void ShowList(EA.Repository repository, string title) { }
private void SendKeyToWindow(IntPtr hWnd, byte vkCode, bool keyDown)
{
uint msg = keyDown ? WM_KEYDOWN : WM_KEYUP;
PostMessage(hWnd, msg, (IntPtr)vkCode, IntPtr.Zero);
}
private void SendKeyPressToWindow(IntPtr hWnd, byte vkCode)
{
SendKeyToWindow(hWnd, vkCode, true);
Thread.Sleep(20);
SendKeyToWindow(hWnd, vkCode, false);
}
private void SendString(IntPtr hWnd, string text)
{
foreach (char c in text)
{
PostMessage(hWnd, WM_CHAR, (IntPtr)c, IntPtr.Zero);
Thread.Sleep(5);
}
}
private void DisableQuickEditForProcess(Process process)
{
if (process == null || process.HasExited) return;
if (!AttachConsole((uint)process.Id)) return;
IntPtr consoleInput = GetStdHandle(STD_INPUT_HANDLE);
if (GetConsoleMode(consoleInput, out uint mode))
{
mode &= ~ENABLE_QUICK_EDIT_MODE;
mode |= ENABLE_EXTENDED_FLAGS;
SetConsoleMode(consoleInput, mode);
}
FreeConsole();
}
private bool IsClaudeInstalled()
{
try
{
Process whereProcess = new Process();
whereProcess.StartInfo.FileName = "where";
whereProcess.StartInfo.Arguments = "claude";
whereProcess.StartInfo.UseShellExecute = false;
whereProcess.StartInfo.RedirectStandardOutput = true;
whereProcess.StartInfo.CreateNoWindow = true;
whereProcess.Start();
string output = whereProcess.StandardOutput.ReadToEnd();
whereProcess.WaitForExit(1000);
return !string.IsNullOrEmpty(output.Trim());
}
catch
{
return false;
}
}
private void StartCmdAndEmbed()
{
try
{
bool hasClaude = IsClaudeInstalled();
string arguments = hasClaude ? "/k claude --dangerously-skip-permissions" : "/k";
cmdProcess = new Process();
cmdProcess.StartInfo.FileName = "cmd.exe";
cmdProcess.StartInfo.Arguments = arguments;
cmdProcess.StartInfo.UseShellExecute = true;
cmdProcess.StartInfo.CreateNoWindow = false;
cmdProcess.StartInfo.WindowStyle = ProcessWindowStyle.Normal;
// 设置工作目录:优先使用用户指定的 WorkingDirectory,其次使用 Model.GetCodePath()
string workDir = !string.IsNullOrEmpty(WorkingDirectory) ? WorkingDirectory : Model.GetCodePath();
cmdProcess.StartInfo.WorkingDirectory = workDir;
cmdProcess.Start();
_isProcessStarted = true;
IntPtr cmdHandle = IntPtr.Zero;
for (int i = 0; i < 50; i++)
{
Thread.Sleep(100);
cmdProcess.Refresh();
cmdHandle = cmdProcess.MainWindowHandle;
if (cmdHandle != IntPtr.Zero)
break;
}
if (cmdHandle == IntPtr.Zero)
throw new Exception("未能在规定时间内获取 CMD 窗口句柄");
this.Invoke(new Action(() =>
{
int style = GetWindowLong(cmdHandle, GWL_STYLE);
style = (style & ~WS_CAPTION & ~WS_BORDER) | WS_CHILD | WS_VISIBLE;
SetWindowLong(cmdHandle, GWL_STYLE, style);
SetParent(cmdHandle, cmdPanel.Handle);
MoveWindow(cmdHandle, 0, 0, cmdPanel.Width, cmdPanel.Height, true);
ShowWindow(cmdHandle, SW_SHOW);
SetActiveWindow(cmdHandle);
BringWindowToTop(cmdHandle);
SetForegroundWindow(cmdHandle);
SetFocus(cmdHandle);
_isEmbedded = true;
DisableQuickEditForProcess(cmdProcess);
if (hasClaude && AutoExecute)
{
System.Windows.Forms.Timer autoTimer = new System.Windows.Forms.Timer();
int step = 0;
autoTimer.Interval = 2000;
autoTimer.Tick += (s, args) =>
{
if (!_isProcessStarted || cmdProcess == null || cmdProcess.HasExited)
{
autoTimer.Stop();
autoTimer.Dispose();
return;
}
IntPtr h = cmdProcess.MainWindowHandle;
if (h == IntPtr.Zero) return;
SetActiveWindow(h);
BringWindowToTop(h);
SetForegroundWindow(h);
SetFocus(h);
Thread.Sleep(50);
if (step == 0)
{
SendKeyPressToWindow(h, VK_DOWN);
SendKeyPressToWindow(h, VK_RETURN);
step++;
autoTimer.Interval = 1500;
}
else if (step == 1)
{
SendString(h, "!pwd");
SendKeyPressToWindow(h, VK_RETURN);
autoTimer.Stop();
autoTimer.Dispose();
}
};
autoTimer.Start();
}
}));
}
catch (Exception ex)
{
this.Invoke(new Action(() =>
{
MessageBox.Show($"启动嵌入式 CMD 失败:{ex.Message}", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}));
Debug.WriteLine(ex.ToString());
}
}
private void UserControlCmdHandle_Resize(object sender, EventArgs e)
{
if (!_isEmbedded || cmdPanel == null || cmdProcess == null)
return;
if (!_isProcessStarted || cmdProcess.HasExited)
return;
IntPtr cmdHandle;
try
{
cmdHandle = cmdProcess.MainWindowHandle;
}
catch (InvalidOperationException)
{
return;
}
if (cmdHandle != IntPtr.Zero)
{
MoveWindow(cmdHandle, 0, 0, cmdPanel.Width, cmdPanel.Height, true);
}
}
private void UserControlCmdHandle_Enter(object sender, EventArgs e)
{
ActivateCmdWindow();
}
private void UserControlCmdHandle_MouseDown(object sender, MouseEventArgs e)
{
// 直接激活 CMD 窗口,而不只是让 UserControl 获得焦点
ActivateCmdWindow();
}
private void ActivateCmdWindow()
{
if (!_isEmbedded || cmdProcess == null || !_isProcessStarted || cmdProcess.HasExited)
return;
IntPtr hWnd;
try
{
hWnd = cmdProcess.MainWindowHandle;
}
catch (InvalidOperationException)
{
return;
}
if (hWnd == IntPtr.Zero) return;
// 1. 先暂时移除子窗口样式,恢复为顶级窗口(但父窗口仍为 Panel 时不可见,所以仅瞬间)
int style = GetWindowLong(hWnd, GWL_STYLE);
SetWindowLong(hWnd, GWL_STYLE, style & ~WS_CHILD);
// 2. 重新设置为子窗口并重新嵌入
style = GetWindowLong(hWnd, GWL_STYLE);
style = (style & ~WS_CAPTION & ~WS_BORDER) | WS_CHILD | WS_VISIBLE;
SetWindowLong(hWnd, GWL_STYLE, style);
SetParent(hWnd, cmdPanel.Handle);
MoveWindow(hWnd, 0, 0, cmdPanel.Width, cmdPanel.Height, true);
// 3. 强制激活窗口
ShowWindow(hWnd, SW_SHOW);
SwitchToThisWindow(hWnd, true);
SetForegroundWindow(hWnd);
SetActiveWindow(hWnd);
BringWindowToTop(hWnd);
SetFocus(hWnd);
// 4. 刷新消息队列
Application.DoEvents();
}
}
}