MainForm.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725
  1. using DirectShowLib;
  2. using Microsoft.Extensions.Logging.Console;
  3. using MySql.Data.MySqlClient;
  4. using SinMaiLauncher.ChildProcessHolder;
  5. using SinMaiLauncher.Interops;
  6. using SinMaiLauncher.Models;
  7. namespace SinMaiLauncher;
  8. public partial class MainForm : Form
  9. {
  10. private const int TargetTop = 450;
  11. private const int TargetHeight = 390;
  12. private const int TargetWidth = 520;
  13. private ILogger logger;
  14. private SettingReader setting;
  15. private Dictionary<ChildProcessKind, ChildProcessControlGroup> childProcessControlGroups;
  16. private readonly IEnumerator<string> txtAni = TextAnimation.LoadingTextSeq(8).GetEnumerator();
  17. private string mainControlStatusText = "摸鱼中";
  18. private bool mainControlStatusTextAnimation = false;
  19. private DateTime lastCheckSinMaiMainWinPos = DateTime.Now;
  20. private DateTime lastCheckSysVolume = DateTime.Now;
  21. private List<string> holdingExplorerWindowPaths = new();
  22. private CameraInterops.CameraCaptureWrap? cameraCapture;
  23. private bool cameraMax = false;
  24. public MainForm()
  25. {
  26. InitializeComponent();
  27. }
  28. private WindowInterops.MoveWindowResult MoveSinMaiMainWindow()
  29. {
  30. var pb = (ChildProcessStateBagForWin)childProcessControlGroups[ChildProcessKind.SinMai].StateBag;
  31. //WS_CLIPSIBLINGS WS_POPUPWINDOW WS_VISIBLE
  32. var rc = setting.Settings.AutoRect;
  33. return WindowInterops.TryMoveWindow(pb.HWndMainWindow.Value, rc.Left, rc.Top, rc.Width, rc.Height);
  34. }
  35. private void ExplorerSaveAndKill()
  36. {
  37. holdingExplorerWindowPaths.Clear();
  38. var lst = ExplorerInterops.GetOpenWindows();
  39. logger.LogInformation($"explorer saved paths:{lst.Length}");
  40. holdingExplorerWindowPaths.AddRange(lst);
  41. ExplorerInterops.KillExplorer();
  42. }
  43. private async Task ExplorerRestore()
  44. {
  45. ExplorerInterops.RestoreExplorer();
  46. await Task.Delay(1000);
  47. ExplorerInterops.OpenWindow(holdingExplorerWindowPaths);
  48. }
  49. private async void MainForm_Shown(object sender, EventArgs e)
  50. {
  51. TopMost = false;
  52. TopMost = true;
  53. tcMain.SelectedTab = tpConsoles;
  54. ConsoleInterops.CurrentProcess.ConsoleAllocate();
  55. ConsoleInterops.CurrentProcess.ConsoleDisableQuickEdit();
  56. ConsoleInterops.CurrentProcess.ConsoleWindowSetParent(tpConsoleLauncher);
  57. ConsoleInterops.CurrentProcess.ConsoleWindowFill(tpConsoleLauncher);
  58. tpConsoleLauncher.Resize += delegate { ConsoleInterops.CurrentProcess.ConsoleWindowFill(tpConsoleLauncher); };
  59. //start host application
  60. var builder = Host.CreateDefaultBuilder();
  61. builder.ConfigureServices((ctx, services) =>
  62. {
  63. //控制台日志格式
  64. services.AddLogging(opt =>
  65. {
  66. opt.AddSimpleConsole(p =>
  67. {
  68. p.TimestampFormat = "[dd HH:mm:ss] ";
  69. p.SingleLine = true;
  70. p.ColorBehavior = LoggerColorBehavior.Enabled;
  71. })
  72. .AddDebug()
  73. //.AddProvider(loggingIntoEventBus)
  74. ;
  75. services.AddSingleton(opt);
  76. });
  77. services.AddSingleton(this);
  78. });
  79. var host = builder.Build();
  80. var services = host.Services;
  81. host.Start();
  82. logger = services.GetRequiredService<ILogger<MainForm>>();
  83. ExplorerInterops.logger = logger;
  84. var conf = services.GetRequiredService<IConfiguration>();
  85. var settingModel = new SinMaiLauncherSettingModel();
  86. conf.GetSection("SinMaiLauncher").Bind(settingModel);
  87. setting = new(settingModel);
  88. childProcessControlGroups = new Dictionary<ChildProcessKind, ChildProcessControlGroup>
  89. {
  90. {
  91. ChildProcessKind.MariaDb, new ChildProcessControlGroup
  92. {
  93. StateBag = new ProcessStateBagMariaDb(services.GetRequiredService<ILogger<ProcessStateBagMariaDb>>(), setting),
  94. ConsoleTab = tpConsoleMaria, PidLabel = lblPidMaria, StatusLabel = lblSubControlMaria,
  95. StartButton = btnSubControlMariaStart, StopButton = btnSubControlMariaStop
  96. }
  97. },
  98. {
  99. ChildProcessKind.AquaDx, new ChildProcessControlGroup
  100. {
  101. StateBag = new ProcessStateBagAquaDx(services.GetRequiredService<ILogger<ProcessStateBagAquaDx>>(), setting),
  102. Dep = ChildProcessKind.MariaDb,
  103. ConsoleTab = tpConsoleAquaDx, PidLabel = lblPidAquaDx, StatusLabel = lblSubControlAquaDx,
  104. StartButton = btnSubControlAquaDxStart, StopButton = btnSubControlAquaDxStop
  105. }
  106. },
  107. {
  108. ChildProcessKind.Injector, new ChildProcessControlGroup
  109. {
  110. StateBag = new ChildProcessStateBagForConsole(ChildProcessKind.Injector, setting.Injector),
  111. Dep = ChildProcessKind.AquaDx,
  112. ConsoleTab = tpConsoleInjector, PidLabel = lblPidInjector, StatusLabel = lblSubControlInjector,
  113. StartButton = btnSubControlInjectorStart, StopButton = btnSubControlInjectorStop
  114. }
  115. },
  116. {
  117. ChildProcessKind.SinMai, new ChildProcessControlGroup
  118. {
  119. StateBag = new ChildProcessStateBagForWin(ChildProcessKind.SinMai, setting.SinMai),
  120. Dep = ChildProcessKind.Injector,
  121. ConsoleTab = null, PidLabel = lblPidSinMai, StatusLabel = lblSubControlSinMai,
  122. StartButton = btnSubControlSinMaiStart, StopButton = btnSubControlSinMaiStop
  123. }
  124. }
  125. };
  126. foreach (var kvp in childProcessControlGroups)
  127. {
  128. var kind = kvp.Key;
  129. var gr = kvp.Value;
  130. var tab = gr.ConsoleTab;
  131. var cpb = gr.StateBag;
  132. var btnStart = gr.StartButton;
  133. var btnStop = gr.StopButton;
  134. var lblPid = gr.PidLabel;
  135. var lblStatus = gr.StatusLabel;
  136. //绑定tab控制台窗口填充
  137. if (cpb is ChildProcessStateBagForConsole con)
  138. {
  139. tab.Resize += delegate
  140. {
  141. if (con.HWndConsole.HasValue) WindowInterops.FillWindow(con.HWndConsole.Value, tab);
  142. };
  143. }
  144. btnStart.Click += async delegate
  145. {
  146. if (cpb.IsAlive)
  147. {
  148. MessageBox.Show("它在运行了,别重复点启动啊");
  149. return;
  150. }
  151. if (gr.Dep.HasValue && childProcessControlGroups[gr.Dep.Value].StateBag.Status != ChildProcessStatus.Ready)
  152. {
  153. MessageBox.Show("依赖的进程未就绪,启动不了的,等就绪了再启动。要按顺序启动。");
  154. return;
  155. }
  156. await cpb.StartAsync();
  157. };
  158. btnStop.Click += async delegate
  159. {
  160. if (cpb.IsAlive == false)
  161. {
  162. MessageBox.Show("都没启动,咋停止啊?");
  163. return;
  164. }
  165. await cpb.StopAsync(TimeSpan.FromSeconds(5));
  166. };
  167. cpb.StatusUpdated += async delegate
  168. {
  169. void UpdateUi()
  170. {
  171. lblPid.Text = cpb.Pid.HasValue ? cpb.Pid.Value.ToString() : "-";
  172. lblStatus.Text = cpb.Status switch
  173. {
  174. ChildProcessStatus.NoLaunched => "未启动",
  175. ChildProcessStatus.PreLaunching => "准备启动",
  176. ChildProcessStatus.MissingWorkingDir => "丢失工作目录",
  177. ChildProcessStatus.MissingExeFile => "丢失exe文件",
  178. ChildProcessStatus.Launching => "正在启动",
  179. ChildProcessStatus.WaitingReady => "等待就绪...",
  180. ChildProcessStatus.Ready => "就绪",
  181. ChildProcessStatus.Stopping => "正在停止",
  182. ChildProcessStatus.Stopped => "已停止",
  183. _ => "?"
  184. };
  185. if (cpb.Status == ChildProcessStatus.WaitingReady && cpb is ChildProcessStateBagForConsole con && cpb.IsAlive && cpb.Pid.HasValue && con.HWndConsole.HasValue)
  186. {
  187. if (tab != null)
  188. {
  189. //console 收纳
  190. Program.DisableQuickEdit(cpb.Pid.Value);
  191. WindowInterops.RemoveBorderVisible(con.HWndConsole.Value);
  192. WindowInterops.SetParent(con.HWndConsole.Value, tab.Handle);
  193. WindowInterops.FillWindow(con.HWndConsole.Value, tab);
  194. Height++;
  195. Height--;
  196. }
  197. }
  198. if (chkExplorerAuto.Checked)
  199. {
  200. if (kind == ChildProcessKind.SinMai)
  201. {
  202. if (cpb.Status == ChildProcessStatus.Ready)
  203. {
  204. ExplorerSaveAndKill();
  205. }
  206. if (cpb.Status == ChildProcessStatus.Stopped)
  207. {
  208. logger.LogInformation("SinMai Exit");
  209. _ = ExplorerRestore();
  210. }
  211. }
  212. }
  213. }
  214. if (InvokeRequired) await InvokeAsync(UpdateUi);
  215. else UpdateUi();
  216. };
  217. }
  218. logger.LogInformation("Started");
  219. Application.DoEvents();
  220. await Task.Delay(1000);
  221. tcMain.Enabled = true;
  222. try
  223. {
  224. var cams = CameraInterops.GetAllCamera();
  225. cboCameraDevices.Items.Clear();
  226. foreach (var cam in cams) cboCameraDevices.Items.Add(cam);
  227. if (cboCameraDevices.SelectedItem == null && cams.Length > 0) cboCameraDevices.SelectedIndex = 0;
  228. }
  229. catch (Exception exception)
  230. {
  231. logger.LogError(exception, "搜索摄像头");
  232. }
  233. //#if DEBUG
  234. // tcMain.SelectedTab = tpSubControls;
  235. //#else
  236. tcMain.SelectedTab = tpMainControl;
  237. //#endif
  238. }
  239. private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
  240. {
  241. if (
  242. childProcessControlGroups[ChildProcessKind.MariaDb].StateBag.Status == ChildProcessStatus.Ready
  243. || childProcessControlGroups[ChildProcessKind.AquaDx].StateBag.Status == ChildProcessStatus.Ready
  244. )
  245. {
  246. e.Cancel = MessageBox.Show($"基础设施进程仍在运行。{Environment.NewLine}为了避免进度数据裂开,请确保安全停止再关闭。{Environment.NewLine}要强制关闭吗?", "关闭启动器", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.No;
  247. }
  248. }
  249. private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
  250. {
  251. //Kill all child process
  252. childProcessControlGroups[ChildProcessKind.SinMai].StateBag.StopAsync(TimeSpan.FromSeconds(10)).Wait(TimeSpan.FromSeconds(10));
  253. childProcessControlGroups[ChildProcessKind.Injector].StateBag.StopAsync(TimeSpan.FromSeconds(10)).Wait(TimeSpan.FromSeconds(10));
  254. childProcessControlGroups[ChildProcessKind.AquaDx].StateBag.StopAsync(TimeSpan.FromSeconds(10)).Wait(TimeSpan.FromSeconds(10));
  255. childProcessControlGroups[ChildProcessKind.MariaDb].StateBag.StopAsync(TimeSpan.FromSeconds(10)).Wait(TimeSpan.FromSeconds(10));
  256. cameraCapture?.Dispose();
  257. }
  258. private void tcConsoles_SelectedIndexChanged(object sender, EventArgs e)
  259. {
  260. Height++;
  261. Height--;
  262. }
  263. private async void btnMainControlStart_Click(object sender, EventArgs e)
  264. {
  265. btnMainControlStart.Enabled = false;
  266. mainControlStatusTextAnimation = true;
  267. var orderedProcesses = new List<(ChildProcessKind kind, TimeSpan timeout)>
  268. {
  269. (ChildProcessKind.MariaDb, TimeSpan.FromSeconds(30)),
  270. (ChildProcessKind.AquaDx,TimeSpan.FromSeconds(90)),
  271. (ChildProcessKind.Injector,TimeSpan.FromSeconds(30)),
  272. (ChildProcessKind.SinMai,TimeSpan.FromSeconds(30)),
  273. };
  274. var startedProcesses = new List<ChildProcessKind>();
  275. try
  276. {
  277. foreach (var (kind, timeout) in orderedProcesses)
  278. {
  279. var gr = childProcessControlGroups[kind];
  280. var cpb = gr.StateBag;
  281. mainControlStatusText = $"正在启动 {kind}";
  282. if (cpb.IsAlive)
  283. {
  284. logger.LogWarning("{Kind} is already running, skipping.", kind);
  285. startedProcesses.Add(kind); // 已运行的也记录,便于回滚
  286. continue;
  287. }
  288. await cpb.StartAsync();
  289. await WaitForReadyAsync(cpb, timeout);
  290. if (cpb.Status != ChildProcessStatus.Ready)
  291. {
  292. logger.LogError($"{kind} failed to reach Ready state (current: {cpb.Status}), rolling back.");
  293. await RollbackAsync(startedProcesses);
  294. return;
  295. }
  296. startedProcesses.Add(kind);
  297. }
  298. mainControlStatusText = $"已启动!";
  299. btnMainControlStop.Enabled = true;
  300. mainControlStatusTextAnimation = false;
  301. }
  302. catch (Exception exception)
  303. {
  304. logger.LogError(exception, "Error during startup, rolling back.");
  305. await RollbackAsync(startedProcesses);
  306. }
  307. async Task WaitForReadyAsync(ChildProcessStateBag cpb, TimeSpan timeout)
  308. {
  309. if (cpb.Status == ChildProcessStatus.Ready
  310. || cpb.Status == ChildProcessStatus.MissingWorkingDir
  311. || cpb.Status == ChildProcessStatus.MissingExeFile
  312. || cpb.Status == ChildProcessStatus.Stopped) return;
  313. var tcs = new TaskCompletionSource<bool>();
  314. EventHandler<EventArgs> handler = null;
  315. handler = delegate
  316. {
  317. if (cpb.Status == ChildProcessStatus.Ready
  318. || cpb.Status == ChildProcessStatus.MissingWorkingDir
  319. || cpb.Status == ChildProcessStatus.MissingExeFile
  320. || cpb.Status == ChildProcessStatus.Stopped
  321. )
  322. {
  323. cpb.StatusUpdated -= handler;
  324. tcs.TrySetResult(true);
  325. }
  326. };
  327. cpb.StatusUpdated += handler;
  328. try
  329. {
  330. // 等待 Ready 或超时
  331. await Task.WhenAny(tcs.Task, Task.Delay(timeout));
  332. if (!tcs.Task.IsCompleted)
  333. {
  334. cpb.StatusUpdated -= handler;
  335. throw new TimeoutException($"{cpb.Kind} did not reach Ready state within {timeout.TotalSeconds} seconds.");
  336. }
  337. }
  338. finally
  339. {
  340. cpb.StatusUpdated -= handler; // 确保清理
  341. }
  342. }
  343. async Task RollbackAsync(List<ChildProcessKind> startedProcesses)
  344. {
  345. // 按相反顺序停止
  346. foreach (var kind in startedProcesses.AsEnumerable().Reverse())
  347. {
  348. mainControlStatusText = $"正在停止 {kind}";
  349. var group = childProcessControlGroups[kind];
  350. var cpb = group.StateBag;
  351. if (cpb.IsAlive)
  352. {
  353. logger.LogInformation("Stopping {Kind} during rollback...", kind);
  354. await InvokeAsync(() => group.StatusLabel.Text = "正在停止...");
  355. await cpb.StopAsync(TimeSpan.FromSeconds(5));
  356. }
  357. }
  358. mainControlStatusText = "启动失败,已回滚到启动之前";
  359. btnMainControlStart.Enabled = true;
  360. mainControlStatusTextAnimation = false;
  361. }
  362. }
  363. private async void btnMainControlStop_Click(object sender, EventArgs e)
  364. {
  365. var orderedProcesses = new List<(ChildProcessKind kind, TimeSpan timeout)>
  366. {
  367. (ChildProcessKind.SinMai,TimeSpan.FromSeconds(5)),
  368. (ChildProcessKind.Injector,TimeSpan.FromSeconds(10)),
  369. (ChildProcessKind.AquaDx,TimeSpan.FromSeconds(90)),
  370. (ChildProcessKind.MariaDb, TimeSpan.FromSeconds(30)),
  371. };
  372. foreach (var (kind, timeout) in orderedProcesses)
  373. {
  374. mainControlStatusText = $"正在停止 {kind}";
  375. var gr = childProcessControlGroups[kind];
  376. if (gr.StateBag.IsAlive)
  377. {
  378. await gr.StateBag.StopAsync(timeout);
  379. }
  380. }
  381. btnMainControlStart.Enabled = true;
  382. btnMainControlStop.Enabled = false;
  383. mainControlStatusText = $"已停止";
  384. mainControlStatusTextAnimation = false;
  385. }
  386. private void tmrMain_Tick(object sender, EventArgs e)
  387. {
  388. if (mainControlStatusTextAnimation)
  389. {
  390. txtAni.MoveNext();
  391. lblMainControlStatus.Text = $"{mainControlStatusText} {txtAni.Current}";
  392. }
  393. else
  394. {
  395. lblMainControlStatus.Text = $"{mainControlStatusText}";
  396. }
  397. //窗口调整
  398. if (chkWinTrickAuto.Checked && lastCheckSinMaiMainWinPos < DateTime.Now.AddSeconds(-5))
  399. {
  400. lastCheckSinMaiMainWinPos = DateTime.Now;
  401. var pb = (ChildProcessStateBagForWin)childProcessControlGroups[ChildProcessKind.SinMai].StateBag;
  402. if (pb.Status == ChildProcessStatus.Ready && pb.HWndMainWindow.HasValue)
  403. {
  404. MoveSinMaiMainWindow();
  405. }
  406. }
  407. //显示系统音量
  408. if (lastCheckSysVolume < DateTime.Now.AddSeconds(-1))
  409. {
  410. lastCheckSinMaiMainWinPos = DateTime.Now;
  411. var sv = SystemAudioVolumeInterop.GetVolume();
  412. lblSysVolume.Text = sv.HasValue ? $"{sv:N2}%" : "-";
  413. }
  414. }
  415. private void btnAimeWrite_Click(object sender, EventArgs e)
  416. {
  417. var input = cboAimeCard.Text;
  418. if (string.IsNullOrEmpty(input))
  419. {
  420. MessageBox.Show("倒是输入aime卡号啊?");
  421. return;
  422. }
  423. if (Directory.Exists(setting.AimeFleSinMaiDir) == false)
  424. {
  425. MessageBox.Show("找不到写aime卡文件的目录");
  426. return;
  427. }
  428. if (Directory.Exists(setting.AimeFleSinMaiPath))
  429. {
  430. MessageBox.Show("找不到写aime卡的文件");
  431. return;
  432. }
  433. var aime = input.Replace("-", "");
  434. File.WriteAllText(setting.AimeFleSinMaiPath, aime);
  435. MessageBox.Show($"已将选定的卡号已写入 aime.txt !{Environment.NewLine}在游戏登录时按回车键读卡{Environment.NewLine}(按一次回车不行就多按几次回车键直到读卡成功🙄)");
  436. }
  437. private void btnWinTrick_Click(object sender, EventArgs e)
  438. {
  439. var pb = (ChildProcessStateBagForWin)childProcessControlGroups[ChildProcessKind.SinMai].StateBag;
  440. if (pb.Status != ChildProcessStatus.Ready || !pb.HWndMainWindow.HasValue)
  441. {
  442. MessageBox.Show("游戏启动之后再点这个按钮");
  443. return;
  444. }
  445. var moveWindowResult = MoveSinMaiMainWindow();
  446. switch (moveWindowResult)
  447. {
  448. case WindowInterops.MoveWindowResult.FailGet:
  449. MessageBox.Show("操作不成功:读取失败");
  450. break;
  451. case WindowInterops.MoveWindowResult.FailSet:
  452. MessageBox.Show("操作不成功:设置失败");
  453. break;
  454. case WindowInterops.MoveWindowResult.Success:
  455. MessageBox.Show("操作成功");
  456. break;
  457. case WindowInterops.MoveWindowResult.NoChange:
  458. MessageBox.Show("没有改变");
  459. break;
  460. }
  461. }
  462. private void btnAimeRefresh_Click(object sender, EventArgs e)
  463. {
  464. if (childProcessControlGroups[ChildProcessKind.MariaDb].StateBag.Status != ChildProcessStatus.Ready)
  465. {
  466. MessageBox.Show("启动数据库先啊!");
  467. return;
  468. }
  469. var conf = setting.Settings.Infra.MariaDb;
  470. var connectionString = $"Server={conf.Host};Port={conf.Port};Uid={conf.Usr};Pwd={conf.Pwd};";
  471. using var conn = new MySqlConnection(connectionString);
  472. conn.Open();
  473. using var cmd = conn.CreateCommand();
  474. cmd.CommandText = "SELECT `luid` FROM `aqua`.`sega_card`";
  475. using var dr = cmd.ExecuteReader();
  476. cboAimeCard.Items.Clear();
  477. while (dr.Read())
  478. {
  479. var aimeR = dr["luid"].ToString();
  480. var aimeH = string.Join("-",
  481. aimeR.Substring(0, 4),
  482. aimeR.Substring(4, 4),
  483. aimeR.Substring(8, 4),
  484. aimeR.Substring(12, 4),
  485. aimeR.Substring(16, 4));
  486. cboAimeCard.Items.Add(aimeH);
  487. }
  488. MessageBox.Show($"从数据库中读取到 {cboAimeCard.Items.Count} 个卡号已载入下拉列表");
  489. }
  490. private void btnExplorerKill_Click(object sender, EventArgs e)
  491. {
  492. ExplorerSaveAndKill();
  493. }
  494. private async void btnExplorerRestore_Click(object sender, EventArgs e)
  495. {
  496. await ExplorerRestore();
  497. }
  498. private void btnProcessLoopBackVirtualNic_Click(object sender, EventArgs e)
  499. {
  500. MessageBox.Show("还没做!");
  501. }
  502. private void btnAppDataOverwrite_Click(object sender, EventArgs e)
  503. {
  504. MessageBox.Show("还没做!");
  505. }
  506. private void btnSysVolumeDec_Click(object sender, EventArgs e)
  507. {
  508. var sv = SystemAudioVolumeInterop.GetVolume();
  509. if (!sv.HasValue)
  510. {
  511. MessageBox.Show("无法获取当前音量");
  512. return;
  513. }
  514. sv -= 5;
  515. if (sv < 0) sv = 0;
  516. if (SystemAudioVolumeInterop.SetVolume(sv.Value) == false) MessageBox.Show("音量调整失败");
  517. }
  518. private void btnSysVolumeInc_Click(object sender, EventArgs e)
  519. {
  520. var sv = SystemAudioVolumeInterop.GetVolume();
  521. if (!sv.HasValue)
  522. {
  523. MessageBox.Show("无法获取当前音量");
  524. return;
  525. }
  526. sv += 5;
  527. if (sv > 100) sv = 100;
  528. if (SystemAudioVolumeInterop.SetVolume(sv.Value) == false) MessageBox.Show("音量调整失败");
  529. }
  530. private class ChildProcessControlGroup
  531. {
  532. public ChildProcessStateBag StateBag { get; set; }
  533. public ChildProcessKind? Dep { get; set; }
  534. public TabPage? ConsoleTab { get; set; } // 可空,窗口进程为 null
  535. public Label PidLabel { get; set; }
  536. public Label StatusLabel { get; set; }
  537. public Button StartButton { get; set; }
  538. public Button StopButton { get; set; }
  539. }
  540. private void btnCameraList_Click(object sender, EventArgs e)
  541. {
  542. var cams = CameraInterops.GetAllCamera();
  543. if (cams.Length == 0) MessageBox.Show("笑死,一个摄像头都没找到!");
  544. cboCameraDevices.Items.Clear();
  545. foreach (var cam in cams) cboCameraDevices.Items.Add(cam);
  546. if (cboCameraDevices.SelectedItem == null && cams.Length > 0) cboCameraDevices.SelectedIndex = 0;
  547. }
  548. private void btnCameraOpen_Click(object sender, EventArgs e)
  549. {
  550. if (cboCameraDevices.SelectedItem == null)
  551. {
  552. MessageBox.Show("还没选择要打开的摄像头!");
  553. return;
  554. }
  555. cameraCapture?.Dispose();
  556. cameraCapture = new CameraInterops.CameraCaptureWrap((DsDevice)cboCameraDevices.SelectedItem, picBoxCamera);
  557. cboCameraFormats.Items.Clear();
  558. foreach (var format in cameraCapture.SupportedFormats) cboCameraFormats.Items.Add(format);
  559. var bestFormat = cameraCapture.FindBestFormat(TargetWidth, TargetHeight, 60);
  560. if (bestFormat != null) cboCameraFormats.SelectedItem = bestFormat;
  561. }
  562. private void btnCameraClose_Click(object sender, EventArgs e)
  563. {
  564. cameraCapture?.Dispose();
  565. cameraCapture = null;
  566. }
  567. private void cboCameraFormats_SelectedIndexChanged(object sender, EventArgs e)
  568. {
  569. if (cboCameraFormats.SelectedItem != null)
  570. cameraCapture?.ApplySelectedFormat((CameraInterops.CameraCaptureWrap.ICameraFormat)cboCameraFormats.SelectedItem);
  571. }
  572. private void picBoxCamera_CameraClick(object sender, EventArgs e)
  573. {
  574. if (cameraMax)
  575. {
  576. FormBorderStyle = FormBorderStyle.Sizable;
  577. picBoxCamera.Parent = pnlCamera;
  578. WindowInterops.Normal(picBoxCamera.Handle);
  579. cameraMax = false;
  580. }
  581. else
  582. {
  583. FormBorderStyle = FormBorderStyle.None;
  584. picBoxCamera.Parent = this;
  585. WindowInterops.Maximize(picBoxCamera.Handle);
  586. Top = TargetTop;
  587. Height = TargetHeight;
  588. if (cboCameraFormats.SelectedItem is CameraInterops.CameraCaptureWrap.ICameraFormat fmt)
  589. {
  590. var r = (double)fmt.Width / fmt.Height;
  591. // r = w / h
  592. // w = h * r
  593. Width = (int)(Height * r);
  594. }
  595. cameraMax = true;
  596. }
  597. if (rdoCameraPlaceLeft.Checked)
  598. {
  599. Left = 0;
  600. }
  601. else if (rdoCameraPlaceRight.Checked)
  602. {
  603. Left = Screen.PrimaryScreen.Bounds.Right - Width;
  604. }
  605. }
  606. }