Mac OS X ambilight
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 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. //
  2. // AppDelegate.m
  3. // DisplayBacklight
  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 "Screenshot.h"
  11. // These are the values stored persistently in the preferences
  12. #define PREF_SERIAL_PORT @"SerialPort"
  13. #define PREF_BRIGHTNESS @"Brightness"
  14. #define DISPLAY_DELAY (1.0 / 10.0)
  15. @interface AppDelegate ()
  16. @property (weak) IBOutlet NSMenu *statusMenu;
  17. @property (weak) IBOutlet NSMenu *menuPorts;
  18. @property (weak) IBOutlet NSMenuItem *buttonAmbilight;
  19. @property (weak) IBOutlet NSMenuItem *brightnessItem;
  20. @property (weak) IBOutlet NSSlider *brightnessSlider;
  21. @property (weak) IBOutlet NSMenuItem *brightnessLabel;
  22. @property (strong) NSStatusItem *statusItem;
  23. @property (strong) NSImage *statusImage;
  24. @property (strong) NSTimer *timer;
  25. @property (strong) Serial *serial;
  26. @property (strong) NSArray *lastDisplayIDs;
  27. @property (assign) BOOL restartAmbilight;
  28. @end
  29. @implementation AppDelegate
  30. @synthesize statusMenu, application;
  31. @synthesize menuPorts, buttonAmbilight;
  32. @synthesize brightnessItem, brightnessSlider, brightnessLabel;
  33. @synthesize statusItem, statusImage, lastDisplayIDs;
  34. @synthesize timer, serial, restartAmbilight;
  35. - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
  36. serial = [[Serial alloc] init];
  37. timer = nil;
  38. restartAmbilight = NO;
  39. // Set default configuration values
  40. NSUserDefaults *store = [NSUserDefaults standardUserDefaults];
  41. NSMutableDictionary *appDefaults = [NSMutableDictionary dictionaryWithObject:@"" forKey:PREF_SERIAL_PORT];
  42. [appDefaults setObject:[NSNumber numberWithFloat:50.0] forKey:PREF_BRIGHTNESS];
  43. [store registerDefaults:appDefaults];
  44. [store synchronize];
  45. // Load existing configuration values
  46. NSString *savedPort = [store stringForKey:PREF_SERIAL_PORT];
  47. float brightness = [store floatForKey:PREF_BRIGHTNESS];
  48. // Prepare status bar menu
  49. statusImage = [NSImage imageNamed:@"MenuIcon"];
  50. statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
  51. [statusImage setTemplate:YES];
  52. [statusItem setImage:statusImage];
  53. [statusItem setMenu:statusMenu];
  54. // Prepare brightness menu
  55. brightnessItem.view = brightnessSlider;
  56. [brightnessSlider setFloatValue:brightness];
  57. [brightnessLabel setTitle:[NSString stringWithFormat:@"Value: %.0f%%", brightness]];
  58. // Prepare serial port menu
  59. BOOL startTimer = NO;
  60. NSArray *ports = [Serial listSerialPorts];
  61. if ([ports count] > 0) {
  62. [menuPorts removeAllItems];
  63. for (int i = 0; i < [ports count]; i++) {
  64. // Add Menu Item for this port
  65. NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:[ports objectAtIndex:i] action:@selector(selectedSerialPort:) keyEquivalent:@""];
  66. [menuPorts addItem:item];
  67. // Set Enabled if it was used the last time
  68. if ((savedPort != nil) && [[ports objectAtIndex:i] isEqualToString:savedPort]) {
  69. [[menuPorts itemAtIndex:i] setState:NSOnState];
  70. // Try to open serial port
  71. [serial setPortName:savedPort];
  72. if ([serial openPort]) {
  73. // Unselect it when an error occured opening the port
  74. [[menuPorts itemAtIndex:i] setState:NSOffState];
  75. } else {
  76. startTimer = YES;
  77. }
  78. }
  79. }
  80. if (!startTimer) {
  81. // TODO try to find out new UART port name for controller
  82. }
  83. }
  84. [Screenshot init:self];
  85. lastDisplayIDs = [Screenshot listDisplays];
  86. if (startTimer) {
  87. timer = [NSTimer scheduledTimerWithTimeInterval:DISPLAY_DELAY target:self selector:@selector(visualizeDisplay:) userInfo:nil repeats:YES];
  88. [buttonAmbilight setState:NSOnState];
  89. }
  90. }
  91. - (void)applicationWillTerminate:(NSNotification *)aNotification {
  92. // Stop previous timer setting
  93. if (timer != nil) {
  94. [timer invalidate];
  95. timer = nil;
  96. }
  97. // Remove display callback
  98. [Screenshot close:self];
  99. // Turn off all lights if possible
  100. if ([serial isOpen]) {
  101. [self sendNullFrame];
  102. [serial closePort];
  103. }
  104. }
  105. - (IBAction)relistSerialPorts:(id)sender {
  106. // Refill port list
  107. NSArray *ports = [Serial listSerialPorts];
  108. [menuPorts removeAllItems];
  109. for (int i = 0; i < [ports count]; i++) {
  110. // Add Menu Item for this port
  111. NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:[ports objectAtIndex:i] action:@selector(selectedSerialPort:) keyEquivalent:@""];
  112. [menuPorts addItem:item];
  113. // Mark it if it is currently open
  114. if ([serial isOpen]) {
  115. if ([[ports objectAtIndex:i] isEqualToString:[serial portName]]) {
  116. [[menuPorts itemAtIndex:i] setState:NSOnState];
  117. }
  118. }
  119. }
  120. }
  121. - (void)selectedSerialPort:(NSMenuItem *)source {
  122. // Store selection for next start-up
  123. NSUserDefaults *store = [NSUserDefaults standardUserDefaults];
  124. [store setObject:[source title] forKey:PREF_SERIAL_PORT];
  125. [store synchronize];
  126. // De-select all other ports
  127. for (int i = 0; i < [menuPorts numberOfItems]; i++) {
  128. [[menuPorts itemAtIndex:i] setState:NSOffState];
  129. }
  130. // Select only the current port
  131. [source setState:NSOnState];
  132. // Close previously opened port, if any
  133. if ([serial isOpen]) {
  134. [serial closePort];
  135. }
  136. // Stop previous timer setting
  137. if (timer != nil) {
  138. [timer invalidate];
  139. timer = nil;
  140. }
  141. // Turn off ambilight button
  142. [buttonAmbilight setState:NSOffState];
  143. // Try to open selected port
  144. [serial setPortName:[source title]];
  145. if ([serial openPort] != 0) {
  146. [source setState:NSOffState];
  147. }
  148. }
  149. - (IBAction)toggleAmbilight:(NSMenuItem *)sender {
  150. if ([sender state] == NSOnState) {
  151. [sender setState:NSOffState];
  152. // Stop previous timer setting
  153. if (timer != nil) {
  154. [timer invalidate];
  155. timer = nil;
  156. }
  157. } else {
  158. [sender setState:NSOnState];
  159. timer = [NSTimer scheduledTimerWithTimeInterval:DISPLAY_DELAY target:self selector:@selector(visualizeDisplay:) userInfo:nil repeats:YES];
  160. }
  161. }
  162. - (void)stopAmbilight {
  163. restartAmbilight = NO;
  164. if (timer != nil) {
  165. restartAmbilight = YES;
  166. [timer invalidate];
  167. timer = nil;
  168. [buttonAmbilight setState:NSOffState];
  169. }
  170. }
  171. - (void)newDisplayList:(NSArray *)displayIDs {
  172. lastDisplayIDs = displayIDs;
  173. if (restartAmbilight) {
  174. restartAmbilight = NO;
  175. [buttonAmbilight setState:NSOnState];
  176. timer = [NSTimer scheduledTimerWithTimeInterval:DISPLAY_DELAY target:self selector:@selector(visualizeDisplay:) userInfo:nil repeats:YES];
  177. }
  178. }
  179. - (IBAction)brightnessMoved:(NSSlider *)sender {
  180. [brightnessLabel setTitle:[NSString stringWithFormat:@"Value: %.0f%%", [sender floatValue]]];
  181. // Store changed value in preferences
  182. NSUserDefaults *store = [NSUserDefaults standardUserDefaults];
  183. [store setObject:[NSNumber numberWithFloat:[sender floatValue]] forKey:PREF_BRIGHTNESS];
  184. [store synchronize];
  185. }
  186. - (IBAction)showAbout:(id)sender {
  187. [NSApp activateIgnoringOtherApps:YES];
  188. [application orderFrontStandardAboutPanel:self];
  189. }
  190. - (UInt32)calculateAverage:(unsigned char *)data Width:(NSInteger)width Height:(NSInteger)height SPP:(NSInteger)spp Alpha:(BOOL)alpha StartX:(NSInteger)startX StartY:(NSInteger)startY EndX:(NSInteger)endX EndY:(NSInteger)endY {
  191. int redC = 0, greenC = 1, blueC = 2;
  192. if (alpha) {
  193. redC = 1; greenC = 2; blueC = 3;
  194. }
  195. NSInteger xa, xb, ya, yb;
  196. if (startX < endX) {
  197. xa = startX;
  198. xb = endX;
  199. } else {
  200. xa = endX;
  201. xb = startX;
  202. }
  203. if (startY < endY) {
  204. ya = startY;
  205. yb = endY;
  206. } else {
  207. ya = endY;
  208. yb = startY;
  209. }
  210. unsigned long red = 0, green = 0, blue = 0, count = 0;
  211. for (NSInteger i = xa; i < xb; i++) {
  212. for (NSInteger j = ya; j < yb; j++) {
  213. count++;
  214. unsigned long index = i + (j * width);
  215. red += data[(index * spp) + redC];
  216. green += data[(index * spp) + greenC];
  217. blue += data[(index * spp) + blueC];
  218. }
  219. }
  220. red /= count;
  221. green /= count;
  222. blue /= count;
  223. return ((UInt32)red << 16) | ((UInt32)green << 8) | ((UInt32)blue);
  224. }
  225. struct DisplayAssignment {
  226. int n;
  227. int width, height;
  228. };
  229. struct LEDStrand {
  230. int idMin, idMax;
  231. int display;
  232. int startX, startY;
  233. int direction;
  234. int size;
  235. };
  236. #define DIR_LEFT 0
  237. #define DIR_RIGHT 1
  238. #define DIR_UP 2
  239. #define DIR_DOWN 3
  240. // TODO remove first
  241. struct DisplayAssignment displays[] = {
  242. { 0, 1920, 1080 },
  243. { 1, 900, 1600 }
  244. };
  245. struct LEDStrand strands[] = {
  246. { 0, 32, 0, 1920, 1080, DIR_LEFT, 1920 / 33 },
  247. { 33, 51, 0, 0, 1080, DIR_UP, 1080 / 19 },
  248. { 52, 84, 0, 0, 0, DIR_RIGHT, 1920 / 33 },
  249. { 85, 89, 1, 0, 250, DIR_UP, 250 / 5 },
  250. { 90, 106, 1, 0, 0, DIR_RIGHT, 900 / 17 },
  251. { 107, 134, 1, 900, 0, DIR_DOWN, 1600 / 28 },
  252. { 135, 151, 1, 900, 1600, DIR_LEFT, 900 / 17 },
  253. { 152, 155, 1, 0, 1600, DIR_UP, 180 / 4 }
  254. };
  255. UInt8 ledColorData[156 * 3];
  256. #define COLOR_AVERAGE_OTHER_DIMENSION_SIZE 150
  257. - (void)visualizeSingleDisplay:(NSInteger)disp Data:(unsigned char *)data Width:(unsigned long)width Height:(unsigned long)height SPP:(NSInteger)spp Alpha:(BOOL)alpha {
  258. for (int i = 0; i < (sizeof(strands) / sizeof(strands[0])); i++) {
  259. if (strands[i].display == disp) {
  260. // Walk the strand, calculating value for each LED
  261. unsigned long x = strands[i].startX;
  262. unsigned long y = strands[i].startY;
  263. unsigned long blockWidth = COLOR_AVERAGE_OTHER_DIMENSION_SIZE;
  264. unsigned long blockHeight = COLOR_AVERAGE_OTHER_DIMENSION_SIZE;
  265. if ((strands[i].direction == DIR_LEFT) || (strands[i].direction == DIR_RIGHT)) {
  266. blockWidth = strands[i].size;
  267. } else {
  268. blockHeight = strands[i].size;
  269. }
  270. for (int led = strands[i].idMin; led <= strands[i].idMax; led++) {
  271. // First move appropriately in the direction of the strand
  272. unsigned long endX = x, endY = y;
  273. if (strands[i].direction == DIR_LEFT) {
  274. endX -= blockWidth;
  275. } else if (strands[i].direction == DIR_RIGHT) {
  276. endX += blockWidth;
  277. } else if (strands[i].direction == DIR_UP) {
  278. endY -= blockHeight;
  279. } else if (strands[i].direction == DIR_DOWN) {
  280. endY += blockHeight;
  281. }
  282. // But also span the averaging-square in the other dimension, depending on which
  283. // side of the monitor we're at.
  284. if ((strands[i].direction == DIR_LEFT) || (strands[i].direction == DIR_RIGHT)) {
  285. if (y == 0) {
  286. endY = blockHeight;
  287. } else if (y == displays[disp].height) {
  288. endY -= blockHeight;
  289. }
  290. } else {
  291. if (x == 0) {
  292. endX = blockWidth;
  293. } else if (x == displays[disp].width) {
  294. endX -= blockWidth;
  295. }
  296. }
  297. // Calculate average color for this led
  298. UInt32 color = [self calculateAverage:data Width:width Height:height SPP:spp Alpha:alpha StartX:x StartY:y EndX:endX EndY:endY];
  299. ledColorData[led * 3] = (color & 0xFF0000) >> 16;
  300. ledColorData[(led * 3) + 1] = (color & 0x00FF00) >> 8;
  301. ledColorData[(led * 3) + 2] = color & 0x0000FF;
  302. // Move to next LED
  303. if ((strands[i].direction == DIR_LEFT) || (strands[i].direction == DIR_RIGHT)) {
  304. x = endX;
  305. } else {
  306. y = endY;
  307. }
  308. }
  309. }
  310. }
  311. }
  312. - (void)visualizeDisplay:(NSTimer *)time {
  313. //NSLog(@"Running Ambilight-Algorithm (%lu)...", (unsigned long)[lastDisplayIDs count]);
  314. // Create a Screenshot for all connected displays
  315. for (NSInteger i = 0; i < [lastDisplayIDs count]; i++) {
  316. NSBitmapImageRep *screen = [Screenshot screenshot:[lastDisplayIDs objectAtIndex:i]];
  317. unsigned long width = [screen pixelsWide];
  318. unsigned long height = [screen pixelsHigh];
  319. // Ensure we can handle the format of this display
  320. NSInteger spp = [screen samplesPerPixel];
  321. if (((spp != 3) && (spp != 4)) || ([screen isPlanar] == YES) || ([screen numberOfPlanes] != 1)) {
  322. NSLog(@"Unknown image format for %ld (%ld, %c, %ld)!\n", (long)i, (long)spp, ([screen isPlanar] == YES) ? 'p' : 'n', (long)[screen numberOfPlanes]);
  323. continue;
  324. }
  325. // Find out how the color components are ordered
  326. BOOL alpha = NO;
  327. if ([screen bitmapFormat] & NSAlphaFirstBitmapFormat) {
  328. alpha = YES;
  329. }
  330. // Try to find the matching display id for the strand associations
  331. for (int n = 0; n < (sizeof(displays) / sizeof(displays[0])); n++) {
  332. if ((width == displays[n].width) && (height == displays[n].height)) {
  333. unsigned char *data = [screen bitmapData];
  334. [self visualizeSingleDisplay:n Data:data Width:width Height:height SPP:spp Alpha:alpha];
  335. break;
  336. }
  337. }
  338. }
  339. [self sendLEDFrame];
  340. }
  341. - (void)sendLEDFrame {
  342. if ([serial isOpen]) {
  343. [serial sendString:@"xythobuzRGBled"];
  344. [serial sendData:(char *)ledColorData withLength:(sizeof(ledColorData) / sizeof(ledColorData[0]))];
  345. }
  346. }
  347. - (void)sendNullFrame {
  348. for (int i = 0; i < (sizeof(ledColorData) / sizeof(ledColorData[0])); i++) {
  349. ledColorData[i] = 0;
  350. }
  351. [self sendLEDFrame];
  352. }
  353. + (double)map:(double)val FromMin:(double)fmin FromMax:(double)fmax ToMin:(double)tmin ToMax:(double)tmax {
  354. double norm = (val - fmin) / (fmax - fmin);
  355. return (norm * (tmax - tmin)) + tmin;
  356. }
  357. @end