MainForm.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  1. using BsWidget.BeatSaberHttpStatus;
  2. using BsWidget.BsYurHttpStatus;
  3. using BsWidgetShareCodes;
  4. using OpenHardwareMonitor.Hardware;
  5. using System;
  6. using System.Collections.Concurrent;
  7. using System.Collections.Generic;
  8. using System.Drawing;
  9. using System.Drawing.Drawing2D;
  10. using System.IO;
  11. using System.Linq;
  12. using System.Windows.Forms;
  13. namespace BsWidget
  14. {
  15. internal class MainForm : BaseForm
  16. {
  17. private const string FontName = "arial";
  18. private Timer _updateTimer;
  19. private Timer _pcTimer;
  20. private BlockingCollection<UpdateFlags> _queue;
  21. private BeatSaberHttpStatusClient _client;
  22. private BsYurHttpStatusClient _yurClient;
  23. private Computer _computer;
  24. private Font _smallFont = new Font(FontName, 12, FontStyle.Regular, GraphicsUnit.Pixel);
  25. private Font _mediumFont = new Font(FontName, 20, FontStyle.Regular, GraphicsUnit.Pixel);
  26. [Flags]
  27. private enum UpdateFlags
  28. {
  29. BeatMap = 1 << 0,
  30. Performance = 1 << 1,
  31. NoteFullyCut = 1 << 2,
  32. ClearAll = 1 << 3,
  33. RefreshAll = 1 << 4,
  34. CpuAndGpu = 1 << 5,
  35. }
  36. public Image SongIcon { get; set; } = Properties.Resources.sample_cover;
  37. public string SongName { get; set; } = "Song Name";
  38. public string SongSubName { get; set; } = "Song Sub Name";
  39. public string SongArtist { get; set; } = "Song Artist";
  40. public string BeatMapper { get; set; } = "Mapper";
  41. public string Difficulty { get; set; } = "Difficulty";
  42. public double SongBpm { get; set; } = 123;
  43. public double SongNjs { get; set; } = 23;
  44. public int CurrentScore { get; set; } = 12345;
  45. public int CurrentCombo { get; set; } = 120;
  46. public string CurrentRank { get; set; } = "XX";
  47. public int CurrentMaxScore { get; set; }
  48. public int CurrentCutPerSecond { get; set; }
  49. public int CurrentMissed { get; set; }
  50. public float? CpuUsage { get; set; } = 12.3f;
  51. public float? CpuGhz { get; set; } = 2.333f;
  52. public float? GpuUsage { get; set; } = 45.6f;
  53. public YurStatus YurStatus { get; set; } = new YurStatus { HeartRate = 0, KcalPerMin = 0 };
  54. /// <summary> FinalScore,CurrentCombo </summary>
  55. public List<HistoryModel> CutHistory { get; set; }
  56. public class HistoryModel
  57. {
  58. public DateTime Time { get; set; }
  59. public int CutScore { get; set; }
  60. public int CurrentCombo { get; set; }
  61. public float Percent { get; set; }
  62. public int CutsPerSecond { get; set; }
  63. public float HeartRate { get; set; }
  64. public float KcalPerMin { get; set; }
  65. }
  66. public MainForm()
  67. {
  68. KeyPreview = true;
  69. FormBorderStyle = FormBorderStyle.None;
  70. _client = new BeatSaberHttpStatusClient();
  71. _client.Event += BeatSaber_Event;
  72. _yurClient = new BsYurHttpStatusClient();
  73. _yurClient.Event += Yur_Event;
  74. if (Program.NoPerf)
  75. {
  76. }
  77. else
  78. {
  79. _computer = new Computer();
  80. _computer.CPUEnabled = true;
  81. _computer.GPUEnabled = true;
  82. _computer.FanControllerEnabled = false;
  83. _computer.HDDEnabled = false;
  84. _computer.MainboardEnabled = false;
  85. _computer.RAMEnabled = false;
  86. _computer.Open();
  87. _pcTimer = new Timer { Interval = 750 };
  88. _pcTimer.Tick += PcTimer_Tick;
  89. }
  90. _queue = new BlockingCollection<UpdateFlags>();
  91. CutHistory = new List<HistoryModel>();
  92. _updateTimer = new Timer { Interval = 25 };
  93. _updateTimer.Tick += UpdateTimer_Tick;
  94. }
  95. protected override void OnLoad(EventArgs e)
  96. {
  97. Text = "Beat Saber Status Widget";
  98. base.OnLoad(e);
  99. Font = new Font(FontName, 30, FontStyle.Bold, GraphicsUnit.Pixel);
  100. TopMost = false;
  101. WindowState = FormWindowState.Normal;
  102. _updateTimer.Start();
  103. _client.Start();
  104. _yurClient.Start();
  105. _pcTimer?.Start();
  106. }
  107. protected override void OnMouseDoubleClick(MouseEventArgs e)
  108. {
  109. base.OnMouseDoubleClick(e);
  110. _queue.Add(UpdateFlags.ClearAll);
  111. }
  112. protected override void OnShown(EventArgs e)
  113. {
  114. WindowState = FormWindowState.Normal;
  115. Location = Screen.PrimaryScreen.Bounds.Location;
  116. ClientSize = Screen.PrimaryScreen.Bounds.Size;
  117. TopMost = true;
  118. base.OnShown(e);
  119. if (_queue.Count == 0)
  120. {
  121. _queue.Add(UpdateFlags.RefreshAll);
  122. }
  123. }
  124. protected override void OnFormClosing(FormClosingEventArgs e)
  125. {
  126. _updateTimer.Stop();
  127. _pcTimer.Stop();
  128. base.OnFormClosing(e);
  129. }
  130. protected override void OnFormClosed(FormClosedEventArgs e)
  131. {
  132. _client.Stop();
  133. _yurClient.Stop();
  134. _computer?.Close();
  135. base.OnFormClosed(e);
  136. }
  137. protected override void OnKeyDown(KeyEventArgs e)
  138. {
  139. base.OnKeyDown(e);
  140. if (e.KeyCode == Keys.Escape) Application.Exit();
  141. }
  142. private void BeatSaber_Event(object sender, BeatSaberStatusEventArgs e)
  143. {
  144. var flags = (UpdateFlags)0;
  145. if (e.Event == "menu" && CutHistory.Count > 0)
  146. {
  147. lock (CutHistory)
  148. {
  149. CutHistory.Clear();
  150. }
  151. flags |= UpdateFlags.ClearAll;
  152. CurrentMissed = 0;
  153. }
  154. if (null != e.Status.Beatmap)
  155. {
  156. CurrentMissed = 0;
  157. var bytes = Convert.FromBase64String(e.Status.Beatmap.SongCover);
  158. using var stream = new MemoryStream(bytes);
  159. var newImg = Image.FromStream(stream);
  160. var old = SongIcon;
  161. SongIcon = newImg;
  162. old?.Dispose();
  163. SongName = e.Status.Beatmap.SongName;
  164. SongSubName = e.Status.Beatmap.SongSubName;
  165. SongArtist = e.Status.Beatmap.SongAuthorName;
  166. BeatMapper = e.Status.Beatmap.LevelAuthorName;
  167. if (string.IsNullOrEmpty(BeatMapper)) BeatMapper = "Unknown Mapper";
  168. Difficulty = e.Status.Beatmap.Difficulty.ToUpper();
  169. if (Difficulty == "EXPERTPLUS") Difficulty = "EXPERT+";
  170. SongBpm = e.Status.Beatmap.SongBPM;
  171. SongNjs = e.Status.Beatmap.NoteJumpSpeed;
  172. flags |= UpdateFlags.BeatMap;
  173. }
  174. if (null != e.Status.Performance)
  175. {
  176. CurrentScore = e.Status.Performance.Score;
  177. CurrentCombo = e.Status.Performance.Combo;
  178. CurrentRank = e.Status.Performance.Rank;
  179. CurrentMaxScore = e.Status.Performance.CurrentMaxScore;
  180. flags |= UpdateFlags.Performance;
  181. }
  182. if (e.Event == "noteMissed")
  183. {
  184. ++CurrentMissed;
  185. flags |= UpdateFlags.Performance;
  186. }
  187. if (e.Event == "noteFullyCut" && null != e.NoteCut?.FinalScore)
  188. {
  189. lock (CutHistory)
  190. {
  191. var now = DateTime.Now;
  192. var preNow = now.AddSeconds(-1);
  193. CurrentCutPerSecond = CutHistory.Count(p => p.Time > preNow);
  194. CutHistory.Add(new HistoryModel
  195. {
  196. Time = now,
  197. CutScore = e.NoteCut.FinalScore.Value,
  198. CurrentCombo = CurrentCombo,
  199. Percent = CurrentMaxScore == 0 ? 0 : ((float)CurrentScore / CurrentMaxScore),
  200. CutsPerSecond = CurrentCutPerSecond,
  201. HeartRate = YurStatus.HeartRate,
  202. KcalPerMin = YurStatus.KcalPerMin,
  203. });
  204. }
  205. flags |= UpdateFlags.NoteFullyCut;
  206. }
  207. if (flags == 0)
  208. {
  209. }
  210. else
  211. {
  212. _queue.Add(flags);
  213. }
  214. }
  215. private void Yur_Event(object sender, YurStatus e)
  216. {
  217. YurStatus = e;
  218. _queue.Add(UpdateFlags.Performance);
  219. }
  220. private void PcTimer_Tick(object sender, EventArgs e)
  221. {
  222. try
  223. {
  224. foreach (var hardware in _computer.Hardware.Where(p => p.HardwareType == HardwareType.CPU || p.HardwareType == HardwareType.GpuNvidia || p.HardwareType == HardwareType.GpuAti))
  225. {
  226. hardware.Update();
  227. }
  228. }
  229. catch (Exception)
  230. {
  231. return;
  232. }
  233. try
  234. {
  235. var mhz = _computer.Hardware.Where(p => p.HardwareType == HardwareType.CPU).SelectMany(p => p.Sensors)
  236. .Where(p => p.SensorType == SensorType.Clock).Select(p => p.Value).Max();
  237. if (mhz.HasValue)
  238. {
  239. CpuGhz = mhz / 1000f;
  240. }
  241. else
  242. {
  243. CpuGhz = null;
  244. }
  245. }
  246. catch
  247. {
  248. CpuGhz = null;
  249. }
  250. try
  251. {
  252. CpuUsage = _computer.Hardware.Where(p => p.HardwareType == HardwareType.CPU).SelectMany(p => p.Sensors)
  253. .Where(p => p.SensorType == SensorType.Load && p.Name == "CPU Total").Select(p => p.Value).Max();
  254. }
  255. catch
  256. {
  257. CpuUsage = null;
  258. }
  259. try
  260. {
  261. GpuUsage = _computer.Hardware.Where(p => p.HardwareType == HardwareType.GpuNvidia || p.HardwareType == HardwareType.GpuAti).SelectMany(p => p.Sensors)
  262. .Where(p => p.SensorType == SensorType.Load && p.Name == "GPU Core").Select(p => p.Value).Max();
  263. }
  264. catch
  265. {
  266. GpuUsage = null;
  267. }
  268. _queue.Add(UpdateFlags.CpuAndGpu);
  269. }
  270. private void UpdateTimer_Tick(object sender, EventArgs e)
  271. {
  272. if (_queue.Count == 0) return;
  273. UpdateGraphic();
  274. }
  275. protected override void RenderGraphic(Graphics g)
  276. {
  277. const int margin = 10;
  278. const int chHeight = 64;
  279. var chWidth = ViewSize.Width - margin * 2;
  280. if (_queue.Count == 0) return;
  281. var flags = _queue.Take();
  282. if (flags.HasFlag(UpdateFlags.ClearAll)) g.Clear(Color.Transparent);
  283. g.SetHighQuality();
  284. if (flags.HasFlag(UpdateFlags.NoteFullyCut) || flags.HasFlag(UpdateFlags.RefreshAll))
  285. {
  286. const int chLeft = margin;
  287. const int chTop = margin;
  288. var chBottom = chTop + chHeight;
  289. float RecordPixel = 2.5f;
  290. var LineWidth = 1.5f;
  291. var displayCount = (int)(chWidth / RecordPixel);
  292. using var scorePen = new Pen(Color.FromArgb(200, 255, 0, 0), LineWidth) { LineJoin = LineJoin.Round };
  293. using var comboPen = new Pen(Color.FromArgb(200, 0, 0, 255), LineWidth) { LineJoin = LineJoin.Round };
  294. using var percentPen = new Pen(Color.FromArgb(200, Color.Green), LineWidth) { LineJoin = LineJoin.Round };
  295. using var cpsPen = new Pen(Color.Yellow, LineWidth) { LineJoin = LineJoin.Round };
  296. using var heartPen = new Pen(Color.FromArgb(200, Color.White), LineWidth) { LineJoin = LineJoin.Round };
  297. using var calPen = new Pen(Color.FromArgb(200, Color.Lime), LineWidth) { LineJoin = LineJoin.Round };
  298. //clear region 0,210 Wx200
  299. g.ClearRect(Color.Transparent, 0, chTop - 3, ViewSize.Width, chHeight + 6);
  300. //draw chart
  301. using var bgBrush = new SolidBrush(Color.FromArgb(90, 255, 255, 255));
  302. g.FillRectangle(bgBrush, chLeft, chTop, chWidth, chHeight);
  303. g.DrawRectangle(Pens.White, chLeft - 1, chTop - 1, chWidth + 2, chHeight + 2);
  304. if (CutHistory.Count > 1)
  305. {
  306. HistoryModel[] items;
  307. lock (CutHistory)
  308. {
  309. var skip = CutHistory.Count > displayCount ? CutHistory.Count - displayCount : 0;
  310. items = CutHistory.Skip(skip).ToArray();
  311. }
  312. if (items.Length > 1)
  313. {
  314. var minScore = items.Select(p => p.CutScore).Min();
  315. var maxScore = items.Select(p => p.CutScore).Max();
  316. var rngScore = maxScore - minScore;
  317. if (rngScore < 1) rngScore = 1;
  318. var minCombo = items.Select(p => p.CurrentCombo).Min();
  319. var maxCombo = items.Select(p => p.CurrentCombo).Max();
  320. var rngCombo = maxCombo - minCombo;
  321. if (rngCombo < 1) rngCombo = 1;
  322. var minHea = items.Select(p => p.HeartRate).Min();
  323. var maxHea = items.Select(p => p.HeartRate).Max();
  324. var rngHea = maxHea - minHea;
  325. if (rngHea < 1) rngHea = 1;
  326. var minCal = items.Select(p => p.KcalPerMin).Min();
  327. var maxCal = items.Select(p => p.KcalPerMin).Max();
  328. var rngCal = maxCal - minCal;
  329. if (rngCal < 1) rngCal = 1;
  330. var minCps = items.Select(p => p.CutsPerSecond).Min();
  331. var maxCps = items.Select(p => p.CutsPerSecond).Max();
  332. var rngCps = maxCps - minCps;
  333. if (rngCps < 1) rngCps = 1;
  334. var x = chWidth - items.Length * RecordPixel + chLeft + 1;
  335. var scorePts = new List<PointF>();
  336. var comboPts = new List<PointF>();
  337. var percentPts = new List<PointF>();
  338. var heaPts = new List<PointF>();
  339. var calPts = new List<PointF>();
  340. var cpsPts = new List<PointF>();
  341. for (var i = 0; i < items.Length; i++)
  342. {
  343. var item = items[i];
  344. scorePts.Add(new PointF(x + RecordPixel * i, (chBottom - 1) - (chHeight - 2) * ((float)(item.CutScore - minScore) / rngScore)));
  345. comboPts.Add(new PointF(x + RecordPixel * i, (chBottom - 1) - (chHeight - 2) * ((float)(item.CurrentCombo - minCombo) / rngCombo)));
  346. percentPts.Add(new PointF(x + RecordPixel * i, (chBottom - 1) - (chHeight - 2) * item.Percent));
  347. cpsPts.Add(new PointF(x + RecordPixel * i, (chBottom - 1) - (chHeight - 2) * ((float)(item.CutsPerSecond - minCps) / rngCps)));
  348. heaPts.Add(new PointF(x + RecordPixel * i, (chBottom - 1) - (chHeight - 2) * ((item.HeartRate - minHea) / rngHea)));
  349. calPts.Add(new PointF(x + RecordPixel * i, (chBottom - 1) - (chHeight - 2) * ((item.KcalPerMin - minCal) / rngCal)));
  350. }
  351. g.DrawLines(scorePen, scorePts.ToArray());
  352. g.DrawLines(comboPen, comboPts.ToArray());
  353. g.DrawLines(percentPen, percentPts.ToArray());
  354. g.DrawLines(cpsPen, cpsPts.ToArray());
  355. g.DrawLines(heartPen, heaPts.ToArray());
  356. g.DrawLines(calPen, calPts.ToArray());
  357. }
  358. }
  359. }
  360. if (flags.HasFlag(UpdateFlags.Performance) || flags.HasFlag(UpdateFlags.RefreshAll))
  361. {
  362. float x = margin;
  363. float y = chHeight + margin * 2;
  364. //clear region 0,180 Wx180
  365. g.ClearRect(Color.Transparent, 0, y, ViewSize.Width, 50);
  366. //draw text
  367. var sz = g.DrawStringWithOutline($"{CurrentScore:N0}", Font, x, y, Color.Black, Color.White);
  368. x += sz.Width + margin * 2;
  369. sz = g.DrawStringWithRoundedRect("POINT", _smallFont, x, y + margin * 1.5f, Brushes.White, Brushes.Red,
  370. 2.5f);
  371. x += sz.Width + margin * 2;
  372. sz = g.DrawStringWithOutline($"{CurrentCombo}", Font, x, y, Color.Black, Color.White);
  373. x += sz.Width + margin * 2;
  374. sz = g.DrawStringWithRoundedRect("COMBO", _smallFont, x, y + margin * 1.5f, Brushes.White, Brushes.Blue,
  375. 2.5f);
  376. x += sz.Width + margin * 2;
  377. sz = g.DrawStringWithOutline($"{CurrentRank}", Font, x, y, Color.Black, Color.White);
  378. x += sz.Width + margin * 2;
  379. sz = g.DrawStringWithRoundedRect("RANK", _smallFont, x, y + margin * 1.5f, Brushes.White,
  380. Brushes.Green, 2.5f);
  381. x += sz.Width + margin * 2;
  382. sz = g.DrawStringWithOutline($"{CurrentCutPerSecond}", Font, x, y, Color.Black, Color.White);
  383. x += sz.Width + margin * 2;
  384. sz = g.DrawStringWithRoundedRect("CUT / SEC", _smallFont, x, y + margin * 1.5f, Brushes.Black,
  385. Brushes.Yellow, 2.5f);
  386. x += sz.Width + margin * 2;
  387. sz = g.DrawStringWithOutline($"{YurStatus.HeartRate:N1}", Font, x, y, Color.Black, Color.White);
  388. x += sz.Width + margin * 2;
  389. sz = g.DrawStringWithRoundedRect("HEART", _smallFont, x, y + margin * 1.5f, Brushes.Black,
  390. Brushes.White, 2.5f);
  391. x += sz.Width + margin * 2;
  392. sz = g.DrawStringWithOutline($"{YurStatus.KcalPerMin:N1}", Font, x, y, Color.Black, Color.White);
  393. x += sz.Width + margin * 2;
  394. sz = g.DrawStringWithRoundedRect("KCAL / MIN", _smallFont, x, y + margin * 1.5f, Brushes.Black,
  395. Brushes.Lime, 2.5f);
  396. x += sz.Width + margin * 2;
  397. sz = g.DrawStringWithOutline($"{CurrentMissed}", Font, x, y, Color.Black, Color.White);
  398. x += sz.Width + margin * 2;
  399. // ReSharper disable once RedundantAssignment
  400. sz = g.DrawStringWithRoundedRect("MISS", _smallFont, x, y + margin * 1.5f, Brushes.White,
  401. Brushes.Purple, 2.5f);
  402. }
  403. if (flags.HasFlag(UpdateFlags.BeatMap) || flags.HasFlag(UpdateFlags.RefreshAll))
  404. {
  405. TopMost = false;
  406. Application.DoEvents();
  407. TopMost = true;
  408. Application.DoEvents();
  409. BringToFront();
  410. Application.DoEvents();
  411. const int coverSize = 130;
  412. g.ClearRect(Color.Transparent, 0, ViewSize.Height - (coverSize + 100), ViewSize.Width, coverSize + 100);
  413. var coverLeft = margin;
  414. var coverTop = ViewSize.Height - coverSize - margin;
  415. g.DrawImage(SongIcon, coverLeft, coverTop, coverSize, coverSize);
  416. g.DrawRectangle(Pens.White, coverLeft, coverTop, coverSize, coverSize);
  417. g.DrawStringWithRoundedRect(Difficulty, Font, coverLeft, coverTop - 100, Brushes.Black, Brushes.White);
  418. {
  419. float x = margin;
  420. var y = coverTop - 50;
  421. var sz = g.DrawStringWithOutline($"{SongBpm}", Font, x, y, Color.Black, Color.White);
  422. x += sz.Width + margin * 2;
  423. sz = g.DrawStringWithRoundedRect("BPM", _smallFont, x, y + margin * 1.5f, Brushes.Black, Brushes.White, 2.5f);
  424. x += sz.Width + margin * 2;
  425. sz = g.DrawStringWithOutline($"{SongNjs}", Font, x, y, Color.Black, Color.White);
  426. x += sz.Width + margin * 2;
  427. // ReSharper disable once RedundantAssignment
  428. sz = g.DrawStringWithRoundedRect("NJS", _smallFont, x, y + margin * 1.5f, Brushes.Black, Brushes.White, 2.5f);
  429. }
  430. {
  431. var bootom = ViewSize.Height - margin;
  432. var left = coverSize + margin * 2;
  433. var fontDeltaH = 1.3f;
  434. var sz = g.MeasureString(SongName, _mediumFont);
  435. g.DrawStringWithOutline(SongName, _mediumFont, left, bootom - sz.Height * fontDeltaH, Color.Black, Color.White);
  436. sz = g.MeasureString(SongSubName, _mediumFont);
  437. g.DrawStringWithOutline(SongSubName, _mediumFont, left, bootom - (sz.Height * fontDeltaH) * 2, Color.Black, Color.White);
  438. sz = g.MeasureString(SongArtist, _mediumFont);
  439. g.DrawStringWithOutline(SongArtist, _mediumFont, left, bootom - (sz.Height * fontDeltaH) * 3, Color.Black, Color.White);
  440. sz = g.MeasureString(BeatMapper, _mediumFont);
  441. g.DrawStringWithOutline(BeatMapper, _mediumFont, left, bootom - (sz.Height * fontDeltaH) * 4, Color.Black, Color.White);
  442. }
  443. }
  444. if (Program.NoPerf == false && (flags.HasFlag(UpdateFlags.CpuAndGpu) || flags.HasFlag(UpdateFlags.RefreshAll)))
  445. {
  446. var width = 800;
  447. var height = 50;
  448. var field = 400;
  449. var left = ViewSize.Width - width;
  450. var top = ViewSize.Height - height;
  451. g.ClearRect(Color.Transparent, left, top, width, height);
  452. g.DrawStringWithOutline($"CPU: {CpuGhz:N1}GHz {CpuUsage:N1}%", Font, left, top, Color.Black, Color.White);
  453. g.DrawStringWithOutline($"GPU: {GpuUsage:N0}%", Font, left + field, top, Color.Black, Color.White);
  454. }
  455. }
  456. }
  457. }