MainForm.cs 21 KB

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