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

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