Simple RGB LED controller for Mac OS X
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AppDelegate.m 31KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877
  1. //
  2. // AppDelegate.m
  3. // CaseLights
  4. //
  5. // Created by Thomas Buck on 21.12.15.
  6. // Copyright © 2015 xythobuz. All rights reserved.
  7. //
  8. #import "AppDelegate.h"
  9. #import "Serial.h"
  10. #import "GPUStats.h"
  11. #import "Screenshot.h"
  12. #import "SystemInfoKit/SystemInfoKit.h"
  13. #define PREF_SERIAL_PORT @"SerialPort"
  14. #define PREF_LIGHTS_STATE @"LightState"
  15. #define PREF_LED_MODE @"LEDMode"
  16. #define PREF_BRIGHTNESS @"Brightness"
  17. #define TEXT_CPU_USAGE @"CPU Usage"
  18. #define TEXT_RAM_USAGE @"RAM Usage"
  19. #define TEXT_GPU_USAGE @"GPU Usage"
  20. #define TEXT_VRAM_USAGE @"VRAM Usage"
  21. #define TEXT_CPU_TEMPERATURE @"CPU Temperature"
  22. #define TEXT_GPU_TEMPERATURE @"GPU Temperature"
  23. #define TEXT_RGB_FADE @"RGB Fade"
  24. #define TEXT_HSV_FADE @"HSV Fade"
  25. #define TEXT_RANDOM @"Random"
  26. #define KEY_CPU_TEMPERATURE @"TC0D"
  27. #define KEY_GPU_TEMPERATURE @"TG0D"
  28. // Temperature in Celsius
  29. #define CPU_TEMP_MIN 20
  30. #define CPU_TEMP_MAX 90
  31. // HSV Color (S = V = 1)
  32. #define CPU_COLOR_MIN 120
  33. #define CPU_COLOR_MAX 0
  34. #define GPU_TEMP_MIN 20
  35. #define GPU_TEMP_MAX 90
  36. #define GPU_COLOR_MIN 120
  37. #define GPU_COLOR_MAX 0
  38. #define RAM_COLOR_MIN 0
  39. #define RAM_COLOR_MAX 120
  40. @interface AppDelegate ()
  41. @property (strong) NSStatusItem *statusItem;
  42. @property (strong) NSImage *statusImage;
  43. @property (strong) NSDictionary *staticColors;
  44. @property (strong) NSTimer *animation;
  45. @property (strong) Serial *serial;
  46. @property (strong) NSMenuItem *lastLEDMode;
  47. @end
  48. @implementation AppDelegate
  49. @synthesize statusMenu, application;
  50. @synthesize menuColors, menuAnimations, menuVisualizations, menuPorts;
  51. @synthesize menuItemDisplays, menuDisplays;
  52. @synthesize buttonOff, buttonLights;
  53. @synthesize brightnessItem, brightnessSlider, brightnessLabel;
  54. @synthesize statusItem, statusImage;
  55. @synthesize staticColors, animation;
  56. @synthesize serial, lastLEDMode;
  57. - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
  58. srand((unsigned)time(NULL));
  59. serial = [[Serial alloc] init];
  60. lastLEDMode = nil;
  61. animation = nil;
  62. // Prepare status bar menu
  63. statusImage = [NSImage imageNamed:@"MenuIcon"];
  64. [statusImage setTemplate:YES];
  65. statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
  66. [statusItem setImage:statusImage];
  67. [statusItem setMenu:statusMenu];
  68. // Set default configuration values, load existing ones
  69. NSUserDefaults *store = [NSUserDefaults standardUserDefaults];
  70. NSMutableDictionary *appDefaults = [NSMutableDictionary dictionaryWithObject:@"" forKey:PREF_SERIAL_PORT];
  71. [appDefaults setObject:[NSNumber numberWithBool:NO] forKey:PREF_LIGHTS_STATE];
  72. [appDefaults setObject:@"" forKey:PREF_LED_MODE];
  73. [appDefaults setObject:[NSNumber numberWithFloat:50.0] forKey:PREF_BRIGHTNESS];
  74. [store registerDefaults:appDefaults];
  75. [store synchronize];
  76. NSString *savedPort = [store stringForKey:PREF_SERIAL_PORT];
  77. BOOL turnOnLights = [store boolForKey:PREF_LIGHTS_STATE];
  78. NSString *lastMode = [store stringForKey:PREF_LED_MODE];
  79. float brightness = [store floatForKey:PREF_BRIGHTNESS];
  80. // Prepare brightness menu
  81. brightnessItem.view = brightnessSlider;
  82. [brightnessSlider setFloatValue:brightness];
  83. [brightnessLabel setTitle:[NSString stringWithFormat:@"Value: %.0f%%", brightness]];
  84. // Prepare serial port menu
  85. NSArray *ports = [Serial listSerialPorts];
  86. if ([ports count] > 0) {
  87. [menuPorts removeAllItems];
  88. for (int i = 0; i < [ports count]; i++) {
  89. // Add Menu Item for this port
  90. NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:[ports objectAtIndex:i] action:@selector(selectedSerialPort:) keyEquivalent:@""];
  91. [item setTag:-1];
  92. [menuPorts addItem:item];
  93. // Set Enabled if it was used the last time
  94. if ((savedPort != nil) && [[ports objectAtIndex:i] isEqualToString:savedPort]) {
  95. [[menuPorts itemAtIndex:i] setState:NSOnState];
  96. // Try to open serial port
  97. [serial setPortName:savedPort];
  98. if ([serial openPort]) {
  99. // Unselect it when an error occured opening the port
  100. [[menuPorts itemAtIndex:i] setState:NSOffState];
  101. }
  102. }
  103. }
  104. }
  105. // Select "Off" button if it was last selected
  106. if ([lastMode isEqualToString:@""]) {
  107. [buttonOff setState:NSOffState];
  108. [self turnLEDsOff:buttonOff];
  109. }
  110. // Prepare static colors menu
  111. staticColors = [NSDictionary dictionaryWithObjectsAndKeys:
  112. [NSColor colorWithCalibratedRed:1.0f green:0.0f blue:0.0f alpha:0.0f], @"Red",
  113. [NSColor colorWithCalibratedRed:0.0f green:1.0f blue:0.0f alpha:0.0f], @"Green",
  114. [NSColor colorWithCalibratedRed:0.0f green:0.0f blue:1.0f alpha:0.0f], @"Blue",
  115. [NSColor colorWithCalibratedRed:0.0f green:1.0f blue:1.0f alpha:0.0f], @"Cyan",
  116. [NSColor colorWithCalibratedRed:1.0f green:0.0f blue:1.0f alpha:0.0f], @"Magenta",
  117. [NSColor colorWithCalibratedRed:1.0f green:1.0f blue:0.0f alpha:0.0f], @"Yellow",
  118. [NSColor colorWithCalibratedRed:1.0f green:1.0f blue:1.0f alpha:0.0f], @"White",
  119. nil];
  120. for (NSString *key in [staticColors allKeys]) {
  121. NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:key action:@selector(selectedVisualization:) keyEquivalent:@""];
  122. [item setTag:-1];
  123. if ([key isEqualToString:lastMode]) {
  124. [self selectedVisualization:item];
  125. }
  126. [menuColors addItem:item];
  127. }
  128. // Prepare animations menu
  129. NSArray *animationStrings = [NSArray arrayWithObjects:
  130. TEXT_RGB_FADE,
  131. TEXT_HSV_FADE,
  132. TEXT_RANDOM,
  133. nil];
  134. for (NSString *key in animationStrings) {
  135. NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:key action:@selector(selectedVisualization:) keyEquivalent:@""];
  136. [item setTag:-1];
  137. if ([key isEqualToString:lastMode]) {
  138. [self selectedVisualization:item];
  139. }
  140. [menuAnimations addItem:item];
  141. }
  142. // Add CPU Usage menu item
  143. NSMenuItem *cpuUsageItem = [[NSMenuItem alloc] initWithTitle:TEXT_CPU_USAGE action:@selector(selectedVisualization:) keyEquivalent:@""];
  144. [cpuUsageItem setTag:-1];
  145. if ([lastMode isEqualToString:TEXT_CPU_USAGE]) {
  146. [self selectedVisualization:cpuUsageItem];
  147. }
  148. [menuVisualizations addItem:cpuUsageItem];
  149. // Add Memory Usage item
  150. NSMenuItem *memoryUsageItem = [[NSMenuItem alloc] initWithTitle:TEXT_RAM_USAGE action:@selector(selectedVisualization:) keyEquivalent:@""];
  151. [memoryUsageItem setTag:-1];
  152. if ([lastMode isEqualToString:TEXT_RAM_USAGE]) {
  153. [self selectedVisualization:memoryUsageItem];
  154. }
  155. [menuVisualizations addItem:memoryUsageItem];
  156. // Check if GPU Stats are available, add menu items if so
  157. NSNumber *usage;
  158. NSNumber *freeVRAM;
  159. NSNumber *usedVRAM;
  160. if ([GPUStats getGPUUsage:&usage freeVRAM:&freeVRAM usedVRAM:&usedVRAM] != 0) {
  161. NSLog(@"Error reading GPU information\n");
  162. } else {
  163. NSMenuItem *itemUsage = [[NSMenuItem alloc] initWithTitle:TEXT_GPU_USAGE action:@selector(selectedVisualization:) keyEquivalent:@""];
  164. [itemUsage setTag:-1];
  165. if ([lastMode isEqualToString:TEXT_GPU_USAGE]) {
  166. [self selectedVisualization:itemUsage];
  167. }
  168. [menuVisualizations addItem:itemUsage];
  169. NSMenuItem *itemVRAM = [[NSMenuItem alloc] initWithTitle:TEXT_VRAM_USAGE action:@selector(selectedVisualization:) keyEquivalent:@""];
  170. [itemVRAM setTag:-1];
  171. if ([lastMode isEqualToString:TEXT_VRAM_USAGE]) {
  172. [self selectedVisualization:itemVRAM];
  173. }
  174. [menuVisualizations addItem:itemVRAM];
  175. }
  176. // Check available temperatures and add menu items
  177. JSKSMC *smc = [JSKSMC smc];
  178. for (int i = 0; i < [[smc workingTempKeys] count]; i++) {
  179. NSString *key = [smc.workingTempKeys objectAtIndex:i];
  180. #ifdef DEBUG
  181. NSString *name = [smc humanReadableNameForKey:key];
  182. NSLog(@"Sensor \"%@\": \"%@\"\n", key, name);
  183. #endif
  184. if ([key isEqualToString:KEY_CPU_TEMPERATURE]) {
  185. NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:TEXT_CPU_TEMPERATURE action:@selector(selectedVisualization:) keyEquivalent:@""];
  186. [item setTag:-1];
  187. if ([lastMode isEqualToString:TEXT_CPU_TEMPERATURE]) {
  188. [self selectedVisualization:item];
  189. }
  190. [menuVisualizations addItem:item];
  191. }
  192. if ([key isEqualToString:KEY_GPU_TEMPERATURE]) {
  193. NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:TEXT_GPU_TEMPERATURE action:@selector(selectedVisualization:) keyEquivalent:@""];
  194. [item setTag:-1];
  195. if ([lastMode isEqualToString:TEXT_GPU_TEMPERATURE]) {
  196. [self selectedVisualization:item];
  197. }
  198. [menuVisualizations addItem:item];
  199. }
  200. }
  201. // Restore previously used lights configuration
  202. if (turnOnLights) {
  203. // Turn on lights
  204. if ([serial isOpen]) {
  205. [serial sendString:@"UV 1\n"];
  206. }
  207. [buttonLights setState:NSOnState];
  208. } else {
  209. // Turn off lights
  210. if ([serial isOpen]) {
  211. [serial sendString:@"UV 0\n"];
  212. }
  213. }
  214. // List available displays and add menu items
  215. [Screenshot init:self];
  216. NSArray *displayIDs = [Screenshot listDisplays];
  217. [self updateDisplayUI:displayIDs];
  218. }
  219. - (void)applicationWillTerminate:(NSNotification *)aNotification {
  220. // Stop previous timer setting
  221. if (animation != nil) {
  222. [animation invalidate];
  223. animation = nil;
  224. }
  225. // Remove display callback
  226. [Screenshot close:self];
  227. // Turn off all lights if possible
  228. if ([serial isOpen]) {
  229. [serial sendString:@"RGB 0 0 0\n"];
  230. [serial sendString:@"UV 0\n"];
  231. [serial closePort];
  232. }
  233. }
  234. - (void)clearDisplayUI {
  235. for (int i = 0; i < [menuDisplays numberOfItems]; i++) {
  236. if ([[menuDisplays itemAtIndex:i] isEnabled] == YES) {
  237. // A display configuration is currently selected. Disable the timer
  238. if (animation != nil) {
  239. [animation invalidate];
  240. animation = nil;
  241. }
  242. }
  243. }
  244. [menuDisplays removeAllItems];
  245. [menuItemDisplays setHidden:YES];
  246. }
  247. - (void)updateDisplayUI:(NSArray *)displayIDs {
  248. if ([displayIDs count] > 0) {
  249. NSUserDefaults *store = [NSUserDefaults standardUserDefaults];
  250. NSString *lastMode = [store stringForKey:PREF_LED_MODE];
  251. [menuItemDisplays setHidden:NO];
  252. for (int i = 0; i < [displayIDs count]; i++) {
  253. NSString *title = [Screenshot displayNameFromDisplayID:[displayIDs objectAtIndex:i]];
  254. NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:title
  255. action:@selector(selectedVisualization:)
  256. keyEquivalent:@""];
  257. [item setTag:[[displayIDs objectAtIndex:i] integerValue]];
  258. if ([title isEqualToString:lastMode]) {
  259. [self selectedVisualization:item];
  260. }
  261. [menuDisplays addItem:item];
  262. }
  263. }
  264. }
  265. - (void)setLightsR:(unsigned char)r G:(unsigned char)g B:(unsigned char)b {
  266. if ([serial isOpen]) {
  267. unsigned char red = r * ([brightnessSlider floatValue] / 100.0);
  268. unsigned char green = g * ([brightnessSlider floatValue] / 100.0);
  269. unsigned char blue = b * ([brightnessSlider floatValue] / 100.0);
  270. [serial sendString:[NSString stringWithFormat:@"RGB %d %d %d\n", red, green, blue]];
  271. } else {
  272. #ifdef DEBUG
  273. NSLog(@"Trying to send RGB without opened port!\n");
  274. #endif
  275. }
  276. }
  277. - (IBAction)relistSerialPorts:(id)sender {
  278. // Refill port list
  279. NSArray *ports = [Serial listSerialPorts];
  280. [menuPorts removeAllItems];
  281. for (int i = 0; i < [ports count]; i++) {
  282. // Add Menu Item for this port
  283. NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:[ports objectAtIndex:i] action:@selector(selectedSerialPort:) keyEquivalent:@""];
  284. [item setTag:-1];
  285. [menuPorts addItem:item];
  286. // Mark it if it is currently open
  287. if ([serial isOpen]) {
  288. if ([[ports objectAtIndex:i] isEqualToString:[serial portName]]) {
  289. [[menuPorts itemAtIndex:i] setState:NSOnState];
  290. }
  291. }
  292. }
  293. }
  294. - (IBAction)brightnessMoved:(NSSlider *)sender {
  295. [brightnessLabel setTitle:[NSString stringWithFormat:@"Value: %.0f%%", [sender floatValue]]];
  296. // Restore the current configuration for items where it won't happen automatically
  297. for (int i = 0; i < [menuColors numberOfItems]; i++) {
  298. if ([[menuColors itemAtIndex:i] state] == NSOnState) {
  299. [self selectedVisualization:[menuColors itemAtIndex:i]];
  300. }
  301. }
  302. // Store changed value in preferences
  303. NSUserDefaults *store = [NSUserDefaults standardUserDefaults];
  304. [store setObject:[NSNumber numberWithFloat:[sender floatValue]] forKey:PREF_BRIGHTNESS];
  305. [store synchronize];
  306. }
  307. - (IBAction)turnLEDsOff:(NSMenuItem *)sender {
  308. if ([sender state] == NSOffState) {
  309. lastLEDMode = nil;
  310. // Stop previous timer setting
  311. if (animation != nil) {
  312. [animation invalidate];
  313. animation = nil;
  314. }
  315. // Turn off all other LED menu items
  316. for (int i = 0; i < [menuColors numberOfItems]; i++) {
  317. if ([[menuColors itemAtIndex:i] state] == NSOnState) {
  318. lastLEDMode = [menuColors itemAtIndex:i];
  319. }
  320. [[menuColors itemAtIndex:i] setState:NSOffState];
  321. }
  322. for (int i = 0; i < [menuAnimations numberOfItems]; i++) {
  323. if ([[menuAnimations itemAtIndex:i] state] == NSOnState) {
  324. lastLEDMode = [menuAnimations itemAtIndex:i];
  325. }
  326. [[menuAnimations itemAtIndex:i] setState:NSOffState];
  327. }
  328. for (int i = 0; i < [menuVisualizations numberOfItems]; i++) {
  329. if ([[menuVisualizations itemAtIndex:i] state] == NSOnState) {
  330. lastLEDMode = [menuVisualizations itemAtIndex:i];
  331. }
  332. [[menuVisualizations itemAtIndex:i] setState:NSOffState];
  333. }
  334. for (int i = 0; i < [menuDisplays numberOfItems]; i++) {
  335. if ([[menuDisplays itemAtIndex:i] state] == NSOnState) {
  336. lastLEDMode = [menuDisplays itemAtIndex:i];
  337. }
  338. [[menuDisplays itemAtIndex:i] setState:NSOffState];
  339. }
  340. // Turn on "off" menu item
  341. [sender setState:NSOnState];
  342. // Store changed value in preferences
  343. NSUserDefaults *store = [NSUserDefaults standardUserDefaults];
  344. [store setObject:@"" forKey:PREF_LED_MODE];
  345. [store synchronize];
  346. #ifdef DEBUG
  347. NSLog(@"Stored new mode: \"off\"!\n");
  348. #endif
  349. // Send command to turn off LEDs
  350. [self setLightsR:0 G:0 B:0];
  351. } else {
  352. // Try to restore last LED setting
  353. if (lastLEDMode != nil) {
  354. [self selectedVisualization:lastLEDMode];
  355. }
  356. }
  357. }
  358. - (IBAction)toggleLights:(NSMenuItem *)sender {
  359. if ([sender state] == NSOffState) {
  360. // Turn on lights
  361. if ([serial isOpen]) {
  362. [serial sendString:@"UV 1\n"];
  363. }
  364. [sender setState:NSOnState];
  365. } else {
  366. // Turn off lights
  367. if ([serial isOpen]) {
  368. [serial sendString:@"UV 0\n"];
  369. }
  370. [sender setState:NSOffState];
  371. }
  372. // Store changed value in preferences
  373. NSUserDefaults *store = [NSUserDefaults standardUserDefaults];
  374. [store setBool:([sender state] == NSOnState) forKey:PREF_LIGHTS_STATE];
  375. [store synchronize];
  376. }
  377. - (BOOL)timedVisualization:(NSString *)mode {
  378. // Stop previous timer setting
  379. if (animation != nil) {
  380. [animation invalidate];
  381. animation = nil;
  382. }
  383. // Schedule next invocation for this animation...
  384. if ([mode isEqualToString:TEXT_GPU_USAGE]) {
  385. animation = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(visualizeGPUUsage:) userInfo:mode repeats:YES];
  386. } else if ([mode isEqualToString:TEXT_VRAM_USAGE]) {
  387. animation = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(visualizeVRAMUsage:) userInfo:mode repeats:YES];
  388. } else if ([mode isEqualToString:TEXT_CPU_USAGE]) {
  389. animation = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(visualizeCPUUsage:) userInfo:mode repeats:YES];
  390. } else if ([mode isEqualToString:TEXT_RAM_USAGE]) {
  391. animation = [NSTimer scheduledTimerWithTimeInterval:20.0 target:self selector:@selector(visualizeRAMUsage:) userInfo:mode repeats:YES];
  392. } else if ([mode isEqualToString:TEXT_CPU_TEMPERATURE]) {
  393. animation = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(visualizeCPUTemperature:) userInfo:mode repeats:YES];
  394. } else if ([mode isEqualToString:TEXT_GPU_TEMPERATURE]) {
  395. animation = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(visualizeGPUTemperature:) userInfo:mode repeats:YES];
  396. } else if ([mode isEqualToString:TEXT_RGB_FADE]) {
  397. animation = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(visualizeRGBFade:) userInfo:mode repeats:YES];
  398. } else if ([mode isEqualToString:TEXT_HSV_FADE]) {
  399. animation = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(visualizeHSVFade:) userInfo:mode repeats:YES];
  400. } else if ([mode isEqualToString:TEXT_RANDOM]) {
  401. animation = [NSTimer scheduledTimerWithTimeInterval:0.25 target:self selector:@selector(visualizeRandom:) userInfo:mode repeats:YES];
  402. } else {
  403. return NO;
  404. }
  405. #ifdef DEBUG
  406. NSLog(@"Scheduled animation for \"%@\"!\n", mode);
  407. #endif
  408. // ...and also execute it right now
  409. [animation fire];
  410. return YES;
  411. }
  412. - (void)displayVisualization:(NSMenuItem *)sender {
  413. // Stop previous timer setting
  414. if (animation != nil) {
  415. [animation invalidate];
  416. animation = nil;
  417. }
  418. // Schedule next invocation for this animation...
  419. animation = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(visualizeDisplay:) userInfo:[NSNumber numberWithInteger:[sender tag]] repeats:YES];
  420. // ...and also execute it right now
  421. [animation fire];
  422. }
  423. - (void)selectedVisualization:(NSMenuItem *)sender {
  424. // Turn off all other LED menu items
  425. if (menuColors != nil) {
  426. for (int i = 0; i < [menuColors numberOfItems]; i++) {
  427. [[menuColors itemAtIndex:i] setState:NSOffState];
  428. }
  429. }
  430. if (menuAnimations != nil) {
  431. for (int i = 0; i < [menuAnimations numberOfItems]; i++) {
  432. [[menuAnimations itemAtIndex:i] setState:NSOffState];
  433. }
  434. }
  435. if (menuVisualizations != nil) {
  436. for (int i = 0; i < [menuVisualizations numberOfItems]; i++) {
  437. [[menuVisualizations itemAtIndex:i] setState:NSOffState];
  438. }
  439. }
  440. if (menuDisplays != nil) {
  441. for (int i = 0; i < [menuDisplays numberOfItems]; i++) {
  442. [[menuDisplays itemAtIndex:i] setState:NSOffState];
  443. }
  444. }
  445. [buttonOff setState:NSOffState];
  446. [sender setState:NSOnState];
  447. // Check if it is a display
  448. BOOL found = NO;
  449. if ([sender tag] > -1) {
  450. found = YES;
  451. [self displayVisualization:sender];
  452. }
  453. // Check if a static color was selected
  454. if ((found == NO) && (staticColors != nil)) {
  455. for (NSString *key in [staticColors allKeys]) {
  456. if ([sender.title isEqualToString:key]) {
  457. found = YES;
  458. // Stop previous timer setting
  459. if (animation != nil) {
  460. [animation invalidate];
  461. animation = nil;
  462. }
  463. NSColor *color = [staticColors valueForKey:key];
  464. unsigned char red = [color redComponent] * 255;
  465. unsigned char green = [color greenComponent] * 255;
  466. unsigned char blue = [color blueComponent] * 255;
  467. [self setLightsR:red G:green B:blue];
  468. break;
  469. }
  470. }
  471. }
  472. if (found == NO) {
  473. // Check if an animated visualization was selected
  474. if ([self timedVisualization:[sender title]] == NO) {
  475. NSLog(@"Unknown LED Visualization selected!\n");
  476. return;
  477. }
  478. }
  479. // Store changed value in preferences
  480. NSUserDefaults *store = [NSUserDefaults standardUserDefaults];
  481. [store setObject:[sender title] forKey:PREF_LED_MODE];
  482. [store synchronize];
  483. #ifdef DEBUG
  484. NSLog(@"Stored new mode: \"%@\"!\n", [sender title]);
  485. #endif
  486. }
  487. - (void)selectedSerialPort:(NSMenuItem *)source {
  488. // Store selection for next start-up
  489. NSUserDefaults *store = [NSUserDefaults standardUserDefaults];
  490. [store setObject:[source title] forKey:PREF_SERIAL_PORT];
  491. [store synchronize];
  492. // De-select all other ports
  493. for (int i = 0; i < [menuPorts numberOfItems]; i++) {
  494. [[menuPorts itemAtIndex:i] setState:NSOffState];
  495. }
  496. // Select only the current port
  497. [source setState:NSOnState];
  498. // Close previously opened port, if any
  499. if ([serial isOpen]) {
  500. [serial closePort];
  501. }
  502. // Try to open selected port
  503. [serial setPortName:[source title]];
  504. if ([serial openPort] != 0) {
  505. [source setState:NSOffState];
  506. } else {
  507. // Restore the current configuration
  508. for (int i = 0; i < [menuColors numberOfItems]; i++) {
  509. if ([[menuColors itemAtIndex:i] state] == NSOnState) {
  510. [self selectedVisualization:[menuColors itemAtIndex:i]];
  511. }
  512. }
  513. for (int i = 0; i < [menuAnimations numberOfItems]; i++) {
  514. if ([[menuAnimations itemAtIndex:i] state] == NSOnState) {
  515. [self selectedVisualization:[menuAnimations itemAtIndex:i]];
  516. }
  517. }
  518. for (int i = 0; i < [menuVisualizations numberOfItems]; i++) {
  519. if ([[menuVisualizations itemAtIndex:i] state] == NSOnState) {
  520. [self selectedVisualization:[menuVisualizations itemAtIndex:i]];
  521. }
  522. }
  523. if ([buttonOff state] == NSOnState) {
  524. [buttonOff setState:NSOffState];
  525. [self turnLEDsOff:buttonOff];
  526. }
  527. if ([buttonLights state] == NSOnState) {
  528. [serial sendString:@"UV 1\n"];
  529. } else {
  530. [serial sendString:@"UV 0\n"];
  531. }
  532. }
  533. }
  534. - (IBAction)showAbout:(id)sender {
  535. [NSApp activateIgnoringOtherApps:YES];
  536. [application orderFrontStandardAboutPanel:self];
  537. }
  538. // ------------------------------------------------------
  539. // ------------------- Visualizations -------------------
  540. // ------------------------------------------------------
  541. - (void)visualizeDisplay:(NSTimer *)timer {
  542. NSBitmapImageRep *screen = [Screenshot screenshot:[timer userInfo]];
  543. if ((([screen samplesPerPixel] != 3) && ([screen samplesPerPixel] != 4)) || ([screen isPlanar] == YES) || ([screen numberOfPlanes] != 1)) {
  544. NSLog(@"Unknown image format (%ld, %c, %ld)!\n", (long)[screen samplesPerPixel], ([screen isPlanar] == YES) ? 'p' : 'n', (long)[screen numberOfPlanes]);
  545. return;
  546. }
  547. int redC = 0, greenC = 1, blueC = 2;
  548. if ([screen bitmapFormat] & NSAlphaFirstBitmapFormat) {
  549. redC = 1; greenC = 2; blueC = 3;
  550. }
  551. unsigned char *data = [screen bitmapData];
  552. unsigned long long width = [screen pixelsWide];
  553. unsigned long long height = [screen pixelsHigh];
  554. unsigned long long max = width * height;
  555. unsigned long long red = 0, green = 0, blue = 0;
  556. for (unsigned long long i = 0; i < max; i++) {
  557. red += data[([screen samplesPerPixel] * i) + redC];
  558. green += data[([screen samplesPerPixel] * i) + greenC];
  559. blue += data[([screen samplesPerPixel] * i) + blueC];
  560. }
  561. [self setLightsR:(red / max) G:(green / max) B:(blue / max)];
  562. }
  563. - (void)visualizeGPUUsage:(NSTimer *)timer {
  564. NSNumber *usage;
  565. NSNumber *freeVRAM;
  566. NSNumber *usedVRAM;
  567. if ([GPUStats getGPUUsage:&usage freeVRAM:&freeVRAM usedVRAM:&usedVRAM] != 0) {
  568. NSLog(@"Error reading GPU information\n");
  569. } else {
  570. double h = [self map:[usage doubleValue] FromMin:0.0 FromMax:100.0 ToMin:GPU_COLOR_MIN ToMax:GPU_COLOR_MAX];
  571. #ifdef DEBUG
  572. NSLog(@"GPU Usage: %.3f%%\n", [usage doubleValue]);
  573. #endif
  574. unsigned char r, g, b;
  575. [self convertH:h S:1.0 V:1.0 toR:&r G:&g B:&b];
  576. [self setLightsR:r G:g B:b];
  577. }
  578. }
  579. - (void)visualizeVRAMUsage:(NSTimer *)timer {
  580. NSNumber *usage;
  581. NSNumber *freeVRAM;
  582. NSNumber *usedVRAM;
  583. if ([GPUStats getGPUUsage:&usage freeVRAM:&freeVRAM usedVRAM:&usedVRAM] != 0) {
  584. NSLog(@"Error reading GPU information\n");
  585. } else {
  586. double h = [self map:[freeVRAM doubleValue] FromMin:0.0 FromMax:([freeVRAM doubleValue] + [usedVRAM doubleValue]) ToMin:RAM_COLOR_MIN ToMax:RAM_COLOR_MAX];
  587. #ifdef DEBUG
  588. NSLog(@"VRAM %.2fGB Free + %.2fGB Used = %.2fGB mapped to color %.2f!\n", [freeVRAM doubleValue] / (1024.0 * 1024.0 * 1024.0), [usedVRAM doubleValue] / (1024.0 * 1024.0 * 1024.0), ([freeVRAM doubleValue] + [usedVRAM doubleValue]) / (1024.0 * 1024.0 * 1024.0), h);
  589. #endif
  590. unsigned char r, g, b;
  591. [self convertH:h S:1.0 V:1.0 toR:&r G:&g B:&b];
  592. [self setLightsR:r G:g B:b];
  593. }
  594. }
  595. - (void)visualizeCPUUsage:(NSTimer *)timer {
  596. JSKMCPUUsageInfo cpuUsageInfo = [JSKSystemMonitor systemMonitor].cpuUsageInfo;
  597. double h = [self map:cpuUsageInfo.usage FromMin:0.0 FromMax:100.0 ToMin:CPU_COLOR_MIN ToMax:CPU_COLOR_MAX];
  598. #ifdef DEBUG
  599. NSLog(@"CPU Usage: %.3f%%\n", cpuUsageInfo.usage);
  600. #endif
  601. unsigned char r, g, b;
  602. [self convertH:h S:1.0 V:1.0 toR:&r G:&g B:&b];
  603. [self setLightsR:r G:g B:b];
  604. }
  605. - (void)visualizeRAMUsage:(NSTimer *)timer {
  606. JSKMMemoryUsageInfo memoryUsageInfo = [JSKSystemMonitor systemMonitor].memoryUsageInfo;
  607. double h = [self map:memoryUsageInfo.freeMemory FromMin:0.0 FromMax:(memoryUsageInfo.usedMemory + memoryUsageInfo.freeMemory) ToMin:RAM_COLOR_MIN ToMax:RAM_COLOR_MAX];
  608. #ifdef DEBUG
  609. NSLog(@"RAM %.2fGB Free + %.2fGB Used = %.2fGB mapped to color %.2f!\n", memoryUsageInfo.freeMemory / (1024.0 * 1024.0 * 1024.0), memoryUsageInfo.usedMemory / (1024.0 * 1024.0 * 1024.0), (memoryUsageInfo.freeMemory + memoryUsageInfo.usedMemory) / (1024.0 * 1024.0 * 1024.0), h);
  610. #endif
  611. unsigned char r, g, b;
  612. [self convertH:h S:1.0 V:1.0 toR:&r G:&g B:&b];
  613. [self setLightsR:r G:g B:b];
  614. }
  615. - (void)visualizeGPUTemperature:(NSTimer *)timer {
  616. JSKSMC *smc = [JSKSMC smc];
  617. double temp = [smc temperatureInCelsiusForKey:KEY_GPU_TEMPERATURE];
  618. if (temp > 1000.0) {
  619. temp /= 1000.0;
  620. }
  621. if (temp > GPU_TEMP_MAX) {
  622. temp = GPU_TEMP_MAX;
  623. }
  624. if (temp < GPU_TEMP_MIN) {
  625. temp = GPU_TEMP_MIN;
  626. }
  627. double h = [self map:temp FromMin:GPU_TEMP_MIN FromMax:GPU_TEMP_MAX ToMin:GPU_COLOR_MIN ToMax:GPU_COLOR_MAX];
  628. #ifdef DEBUG
  629. NSLog(@"GPU Temp %.2f mapped to color %.2f!\n", temp, h);
  630. #endif
  631. unsigned char r, g, b;
  632. [self convertH:h S:1.0 V:1.0 toR:&r G:&g B:&b];
  633. [self setLightsR:r G:g B:b];
  634. }
  635. - (void)visualizeCPUTemperature:(NSTimer *)timer {
  636. JSKSMC *smc = [JSKSMC smc];
  637. double temp = [smc temperatureInCelsiusForKey:KEY_CPU_TEMPERATURE];
  638. if (temp > 1000.0) {
  639. temp /= 1000.0;
  640. }
  641. if (temp > CPU_TEMP_MAX) {
  642. temp = CPU_TEMP_MAX;
  643. }
  644. if (temp < CPU_TEMP_MIN) {
  645. temp = CPU_TEMP_MIN;
  646. }
  647. double h = [self map:temp FromMin:CPU_TEMP_MIN FromMax:CPU_TEMP_MAX ToMin:CPU_COLOR_MIN ToMax:CPU_COLOR_MAX];
  648. #ifdef DEBUG
  649. NSLog(@"CPU Temp %.2f mapped to color %.2f!\n", temp, h);
  650. #endif
  651. unsigned char r, g, b;
  652. [self convertH:h S:1.0 V:1.0 toR:&r G:&g B:&b];
  653. [self setLightsR:r G:g B:b];
  654. }
  655. - (void)visualizeRGBFade:(NSTimer *)timer {
  656. static unsigned char color[3] = { 255, 0, 0 };
  657. static int dec = 0;
  658. static int val = 0;
  659. // Adapted from:
  660. // https://gist.github.com/jamesotron/766994
  661. if (dec < 3) {
  662. int inc = (dec == 2) ? 0 : (dec + 1);
  663. if (val < 255) {
  664. color[dec] -= 1;
  665. color[inc] += 1;
  666. val++;
  667. } else {
  668. val = 0;
  669. dec++;
  670. }
  671. } else {
  672. dec = 0;
  673. }
  674. [self setLightsR:color[0] G:color[1] B:color[2]];
  675. }
  676. - (void)visualizeHSVFade:(NSTimer *)timer {
  677. static float h = 0.0;
  678. if (h < 359.0) {
  679. h += 0.5;
  680. } else {
  681. h = 0.0;
  682. }
  683. unsigned char r, g, b;
  684. [self convertH:h S:1.0 V:1.0 toR:&r G:&g B:&b];
  685. [self setLightsR:r G:g B:b];
  686. }
  687. - (void)visualizeRandom:(NSTimer *)timer {
  688. [self setLightsR:rand() % 256 G:rand() % 256 B:rand() % 256];
  689. }
  690. // -----------------------------------------------------
  691. // --------------------- Utilities ---------------------
  692. // -----------------------------------------------------
  693. - (double)map:(double)val FromMin:(double)fmin FromMax:(double)fmax ToMin:(double)tmin ToMax:(double)tmax {
  694. double norm = (val - fmin) / (fmax - fmin);
  695. return (norm * (tmax - tmin)) + tmin;
  696. }
  697. - (void)convertH:(double)h S:(double)s V:(double)v toR:(unsigned char *)r G:(unsigned char *)g B:(unsigned char *)b {
  698. // Adapted from:
  699. // https://gist.github.com/hdznrrd/656996
  700. if (s == 0.0) {
  701. // Achromatic
  702. *r = *g = *b = (unsigned char)(v * 255);
  703. return;
  704. }
  705. h /= 60; // sector 0 to 5
  706. int i = floor(h);
  707. double f = h - i; // factorial part of h
  708. double p = v * (1 - s);
  709. double q = v * (1 - s * f);
  710. double t = v * (1 - s * (1 - f));
  711. switch (i) {
  712. case 0:
  713. *r = (unsigned char)round(255 * v);
  714. *g = (unsigned char)round(255 * t);
  715. *b = (unsigned char)round(255 * p);
  716. break;
  717. case 1:
  718. *r = (unsigned char)round(255 * q);
  719. *g = (unsigned char)round(255 * v);
  720. *b = (unsigned char)round(255 * p);
  721. break;
  722. case 2:
  723. *r = (unsigned char)round(255 * p);
  724. *g = (unsigned char)round(255 * v);
  725. *b = (unsigned char)round(255 * t);
  726. break;
  727. case 3:
  728. *r = (unsigned char)round(255 * p);
  729. *g = (unsigned char)round(255 * q);
  730. *b = (unsigned char)round(255 * v);
  731. break;
  732. case 4:
  733. *r = (unsigned char)round(255 * t);
  734. *g = (unsigned char)round(255 * p);
  735. *b = (unsigned char)round(255 * v);
  736. break;
  737. default: case 5:
  738. *r = (unsigned char)round(255 * v);
  739. *g = (unsigned char)round(255 * p);
  740. *b = (unsigned char)round(255 * q);
  741. break;
  742. }
  743. }
  744. @end