Browse Source

Added Ambilight-clone functionality

Thomas Buck 6 years ago
parent
commit
277c07a099

+ 1
- 1
CaseLights.ino View File

@@ -13,7 +13,7 @@
13 13
  * The UV command turns the UV lights on or off (s can be 0 or 1).
14 14
  */
15 15
 
16
-#define DEBUG
16
+//#define DEBUG
17 17
 
18 18
 enum LoopState {
19 19
   LOOP_IDLE,

+ 6
- 0
CaseLights.xcodeproj/project.pbxproj View File

@@ -10,6 +10,7 @@
10 10
 		E9109B111C2AACF400726111 /* SystemInfoKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E9109B0B1C2AAC1B00726111 /* SystemInfoKit.framework */; };
11 11
 		E9109B151C2AAEFD00726111 /* SystemInfoKit.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = E9109B0B1C2AAC1B00726111 /* SystemInfoKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
12 12
 		E9109B191C2AAFE100726111 /* GPUStats.m in Sources */ = {isa = PBXBuildFile; fileRef = E9109B181C2AAFE100726111 /* GPUStats.m */; };
13
+		E9640E8A1C30511B0081D46C /* Screenshot.m in Sources */ = {isa = PBXBuildFile; fileRef = E9640E891C30511B0081D46C /* Screenshot.m */; };
13 14
 		E9AB18011C29F8CD00BF3C51 /* Serial.m in Sources */ = {isa = PBXBuildFile; fileRef = E9AB18001C29F8CD00BF3C51 /* Serial.m */; };
14 15
 		E9F13E031C28B3D3004C6B95 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E9F13E021C28B3D3004C6B95 /* AppDelegate.m */; };
15 16
 		E9F13E061C28B3D3004C6B95 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E9F13E051C28B3D3004C6B95 /* main.m */; };
@@ -58,6 +59,8 @@
58 59
 		E9109B051C2AAC1B00726111 /* SystemInfoKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SystemInfoKit.xcodeproj; path = JSystemInfoKit/SystemInfoKit.xcodeproj; sourceTree = "<group>"; };
59 60
 		E9109B171C2AAFE100726111 /* GPUStats.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GPUStats.h; path = CaseLights/GPUStats.h; sourceTree = "<group>"; };
60 61
 		E9109B181C2AAFE100726111 /* GPUStats.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GPUStats.m; path = CaseLights/GPUStats.m; sourceTree = "<group>"; };
62
+		E9640E881C30511B0081D46C /* Screenshot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Screenshot.h; path = CaseLights/Screenshot.h; sourceTree = "<group>"; };
63
+		E9640E891C30511B0081D46C /* Screenshot.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Screenshot.m; path = CaseLights/Screenshot.m; sourceTree = "<group>"; };
61 64
 		E9AB17FF1C29F8CD00BF3C51 /* Serial.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Serial.h; path = CaseLights/Serial.h; sourceTree = "<group>"; };
62 65
 		E9AB18001C29F8CD00BF3C51 /* Serial.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Serial.m; path = CaseLights/Serial.m; sourceTree = "<group>"; };
63 66
 		E9F13DFE1C28B3D3004C6B95 /* CaseLights.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CaseLights.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -95,6 +98,8 @@
95 98
 			children = (
96 99
 				E9AB17FF1C29F8CD00BF3C51 /* Serial.h */,
97 100
 				E9AB18001C29F8CD00BF3C51 /* Serial.m */,
101
+				E9640E881C30511B0081D46C /* Screenshot.h */,
102
+				E9640E891C30511B0081D46C /* Screenshot.m */,
98 103
 				E9109B171C2AAFE100726111 /* GPUStats.h */,
99 104
 				E9109B181C2AAFE100726111 /* GPUStats.m */,
100 105
 			);
@@ -255,6 +260,7 @@
255 260
 			files = (
256 261
 				E9AB18011C29F8CD00BF3C51 /* Serial.m in Sources */,
257 262
 				E9109B191C2AAFE100726111 /* GPUStats.m in Sources */,
263
+				E9640E8A1C30511B0081D46C /* Screenshot.m in Sources */,
258 264
 				E9F13E061C28B3D3004C6B95 /* main.m in Sources */,
259 265
 				E9F13E031C28B3D3004C6B95 /* AppDelegate.m in Sources */,
260 266
 			);

+ 5
- 0
CaseLights/AppDelegate.h View File

@@ -18,6 +18,8 @@
18 18
 @property (weak) IBOutlet NSMenu *menuColors;
19 19
 @property (weak) IBOutlet NSMenu *menuAnimations;
20 20
 @property (weak) IBOutlet NSMenu *menuVisualizations;
21
+@property (weak) IBOutlet NSMenuItem *menuItemDisplays;
22
+@property (weak) IBOutlet NSMenu *menuDisplays;
21 23
 @property (weak) IBOutlet NSMenu *menuPorts;
22 24
 
23 25
 @property (weak) IBOutlet NSMenuItem *buttonOff;
@@ -26,5 +28,8 @@
26 28
 @property (weak) IBOutlet NSMenuItem *brightnessLabel;
27 29
 @property (weak) IBOutlet NSMenuItem *buttonLights;
28 30
 
31
+- (void)clearDisplayUI;
32
+- (void)updateDisplayUI:(NSArray *)displayIDs;
33
+
29 34
 @end
30 35
 

+ 121
- 3
CaseLights/AppDelegate.m View File

@@ -9,6 +9,7 @@
9 9
 #import "AppDelegate.h"
10 10
 #import "Serial.h"
11 11
 #import "GPUStats.h"
12
+#import "Screenshot.h"
12 13
 
13 14
 #import "SystemInfoKit/SystemInfoKit.h"
14 15
 
@@ -61,6 +62,7 @@
61 62
 
62 63
 @synthesize statusMenu, application;
63 64
 @synthesize menuColors, menuAnimations, menuVisualizations, menuPorts;
65
+@synthesize menuItemDisplays, menuDisplays;
64 66
 @synthesize buttonOff, buttonLights;
65 67
 @synthesize brightnessItem, brightnessSlider, brightnessLabel;
66 68
 @synthesize statusItem, statusImage;
@@ -106,6 +108,7 @@
106 108
         for (int i = 0; i < [ports count]; i++) {
107 109
             // Add Menu Item for this port
108 110
             NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:[ports objectAtIndex:i] action:@selector(selectedSerialPort:) keyEquivalent:@""];
111
+            [item setTag:-1];
109 112
             [menuPorts addItem:item];
110 113
             
111 114
             // Set Enabled if it was used the last time
@@ -140,6 +143,7 @@
140 143
                     nil];
141 144
     for (NSString *key in [staticColors allKeys]) {
142 145
         NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:key action:@selector(selectedVisualization:) keyEquivalent:@""];
146
+        [item setTag:-1];
143 147
         if ([key isEqualToString:lastMode]) {
144 148
             [self selectedVisualization:item];
145 149
         }
@@ -154,6 +158,7 @@
154 158
                                  nil];
155 159
     for (NSString *key in animationStrings) {
156 160
         NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:key action:@selector(selectedVisualization:) keyEquivalent:@""];
161
+        [item setTag:-1];
157 162
         if ([key isEqualToString:lastMode]) {
158 163
             [self selectedVisualization:item];
159 164
         }
@@ -162,6 +167,7 @@
162 167
     
163 168
     // Add CPU Usage menu item
164 169
     NSMenuItem *cpuUsageItem = [[NSMenuItem alloc] initWithTitle:TEXT_CPU_USAGE action:@selector(selectedVisualization:) keyEquivalent:@""];
170
+    [cpuUsageItem setTag:-1];
165 171
     if ([lastMode isEqualToString:TEXT_CPU_USAGE]) {
166 172
         [self selectedVisualization:cpuUsageItem];
167 173
     }
@@ -169,6 +175,7 @@
169 175
     
170 176
     // Add Memory Usage item
171 177
     NSMenuItem *memoryUsageItem = [[NSMenuItem alloc] initWithTitle:TEXT_RAM_USAGE action:@selector(selectedVisualization:) keyEquivalent:@""];
178
+    [memoryUsageItem setTag:-1];
172 179
     if ([lastMode isEqualToString:TEXT_RAM_USAGE]) {
173 180
         [self selectedVisualization:memoryUsageItem];
174 181
     }
@@ -182,12 +189,14 @@
182 189
         NSLog(@"Error reading GPU information\n");
183 190
     } else {
184 191
         NSMenuItem *itemUsage = [[NSMenuItem alloc] initWithTitle:TEXT_GPU_USAGE action:@selector(selectedVisualization:) keyEquivalent:@""];
192
+        [itemUsage setTag:-1];
185 193
         if ([lastMode isEqualToString:TEXT_GPU_USAGE]) {
186 194
             [self selectedVisualization:itemUsage];
187 195
         }
188 196
         [menuVisualizations addItem:itemUsage];
189 197
         
190 198
         NSMenuItem *itemVRAM = [[NSMenuItem alloc] initWithTitle:TEXT_VRAM_USAGE action:@selector(selectedVisualization:) keyEquivalent:@""];
199
+        [itemVRAM setTag:-1];
191 200
         if ([lastMode isEqualToString:TEXT_VRAM_USAGE]) {
192 201
             [self selectedVisualization:itemVRAM];
193 202
         }
@@ -206,6 +215,7 @@
206 215
 
207 216
         if ([key isEqualToString:KEY_CPU_TEMPERATURE]) {
208 217
             NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:TEXT_CPU_TEMPERATURE action:@selector(selectedVisualization:) keyEquivalent:@""];
218
+            [item setTag:-1];
209 219
             if ([lastMode isEqualToString:TEXT_CPU_TEMPERATURE]) {
210 220
                 [self selectedVisualization:item];
211 221
             }
@@ -214,6 +224,7 @@
214 224
         
215 225
         if ([key isEqualToString:KEY_GPU_TEMPERATURE]) {
216 226
             NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:TEXT_GPU_TEMPERATURE action:@selector(selectedVisualization:) keyEquivalent:@""];
227
+            [item setTag:-1];
217 228
             if ([lastMode isEqualToString:TEXT_GPU_TEMPERATURE]) {
218 229
                 [self selectedVisualization:item];
219 230
             }
@@ -235,9 +246,24 @@
235 246
             [serial sendString:@"UV 0\n"];
236 247
         }
237 248
     }
249
+    
250
+    // List available displays and add menu items
251
+    [Screenshot init:self];
252
+    NSArray *displayIDs = [Screenshot listDisplays];
253
+    [self updateDisplayUI:displayIDs];
238 254
 }
239 255
 
240 256
 - (void)applicationWillTerminate:(NSNotification *)aNotification {
257
+    // Stop previous timer setting
258
+    if (animation != nil) {
259
+        [animation invalidate];
260
+        animation = nil;
261
+    }
262
+    
263
+    // Remove display callback
264
+    [Screenshot close:self];
265
+    
266
+    // Turn off all lights if possible
241 267
     if ([serial isOpen]) {
242 268
         [serial sendString:@"RGB 0 0 0\n"];
243 269
         [serial sendString:@"UV 0\n"];
@@ -245,6 +271,39 @@
245 271
     }
246 272
 }
247 273
 
274
+- (void)clearDisplayUI {
275
+    for (int i = 0; i < [menuDisplays numberOfItems]; i++) {
276
+        if ([[menuDisplays itemAtIndex:i] isEnabled] == YES) {
277
+            // A display configuration is currently selected. Disable the timer
278
+            if (animation != nil) {
279
+                [animation invalidate];
280
+                animation = nil;
281
+            }
282
+        }
283
+    }
284
+    [menuDisplays removeAllItems];
285
+    [menuItemDisplays setHidden:YES];
286
+}
287
+
288
+- (void)updateDisplayUI:(NSArray *)displayIDs {
289
+    if ([displayIDs count] > 0) {
290
+        NSUserDefaults *store = [NSUserDefaults standardUserDefaults];
291
+        NSString *lastMode = [store stringForKey:PREF_LED_MODE];
292
+        [menuItemDisplays setHidden:NO];
293
+        for (int i = 0; i < [displayIDs count]; i++) {
294
+            NSString *title = [Screenshot displayNameFromDisplayID:[displayIDs objectAtIndex:i]];
295
+            NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:title
296
+                                                          action:@selector(selectedVisualization:)
297
+                                                   keyEquivalent:@""];
298
+            [item setTag:[[displayIDs objectAtIndex:i] integerValue]];
299
+            if ([title isEqualToString:lastMode]) {
300
+                [self selectedVisualization:item];
301
+            }
302
+            [menuDisplays addItem:item];
303
+        }
304
+    }
305
+}
306
+
248 307
 - (void)setLightsR:(unsigned char)r G:(unsigned char)g B:(unsigned char)b {
249 308
     if ([serial isOpen]) {
250 309
         unsigned char red = r * ([brightnessSlider floatValue] / 100.0);
@@ -265,6 +324,7 @@
265 324
     for (int i = 0; i < [ports count]; i++) {
266 325
         // Add Menu Item for this port
267 326
         NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:[ports objectAtIndex:i] action:@selector(selectedSerialPort:) keyEquivalent:@""];
327
+        [item setTag:-1];
268 328
         [menuPorts addItem:item];
269 329
         
270 330
         // Mark it if it is currently open
@@ -321,6 +381,12 @@
321 381
             }
322 382
             [[menuVisualizations itemAtIndex:i] setState:NSOffState];
323 383
         }
384
+        for (int i = 0; i < [menuDisplays numberOfItems]; i++) {
385
+            if ([[menuDisplays itemAtIndex:i] state] == NSOnState) {
386
+                lastLEDMode = [menuDisplays itemAtIndex:i];
387
+            }
388
+            [[menuDisplays itemAtIndex:i] setState:NSOffState];
389
+        }
324 390
         
325 391
         // Turn on "off" menu item
326 392
         [sender setState:NSOnState];
@@ -406,6 +472,20 @@
406 472
     return YES;
407 473
 }
408 474
 
475
+- (void)displayVisualization:(NSMenuItem *)sender {
476
+    // Stop previous timer setting
477
+    if (animation != nil) {
478
+        [animation invalidate];
479
+        animation = nil;
480
+    }
481
+    
482
+    // Schedule next invocation for this animation...
483
+    animation = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(visualizeDisplay:) userInfo:[NSNumber numberWithInteger:[sender tag]] repeats:YES];
484
+    
485
+    // ...and also execute it right now
486
+    [animation fire];
487
+}
488
+
409 489
 - (void)selectedVisualization:(NSMenuItem *)sender {
410 490
     // Turn off all other LED menu items
411 491
     if (menuColors != nil) {
@@ -423,12 +503,23 @@
423 503
             [[menuVisualizations itemAtIndex:i] setState:NSOffState];
424 504
         }
425 505
     }
506
+    if (menuDisplays != nil) {
507
+        for (int i = 0; i < [menuDisplays numberOfItems]; i++) {
508
+            [[menuDisplays itemAtIndex:i] setState:NSOffState];
509
+        }
510
+    }
426 511
     [buttonOff setState:NSOffState];
427 512
     [sender setState:NSOnState];
428 513
     
429
-    // Check if a static color was selected
514
+    // Check if it is a display
430 515
     BOOL found = NO;
431
-    if (staticColors != nil) {
516
+    if ([sender tag] > -1) {
517
+        found = YES;
518
+        [self displayVisualization:sender];
519
+    }
520
+    
521
+    // Check if a static color was selected
522
+    if ((found == NO) && (staticColors != nil)) {
432 523
         for (NSString *key in [staticColors allKeys]) {
433 524
             if ([sender.title isEqualToString:key]) {
434 525
                 found = YES;
@@ -450,7 +541,7 @@
450 541
         }
451 542
     }
452 543
     
453
-    if (!found) {
544
+    if (found == NO) {
454 545
         // Check if an animated visualization was selected
455 546
         if ([self timedVisualization:[sender title]] == NO) {
456 547
             NSLog(@"Unknown LED Visualization selected!\n");
@@ -529,6 +620,33 @@
529 620
 // ------------------- Visualizations -------------------
530 621
 // ------------------------------------------------------
531 622
 
623
+- (void)visualizeDisplay:(NSTimer *)timer {
624
+    NSBitmapImageRep *screen = [Screenshot screenshot:[timer userInfo]];
625
+    
626
+    if ((([screen samplesPerPixel] != 3) && ([screen samplesPerPixel] != 4)) || ([screen isPlanar] == YES) || ([screen numberOfPlanes] != 1)) {
627
+        NSLog(@"Unknown image format (%ld, %c, %ld)!\n", (long)[screen samplesPerPixel], ([screen isPlanar] == YES) ? 'p' : 'n', (long)[screen numberOfPlanes]);
628
+        return;
629
+    }
630
+    
631
+    int redC = 0, greenC = 1, blueC = 2;
632
+    if ([screen bitmapFormat] & NSAlphaFirstBitmapFormat) {
633
+        redC = 1; greenC = 2; blueC = 3;
634
+    }
635
+    
636
+    unsigned char *data = [screen bitmapData];
637
+    unsigned long long width = [screen pixelsWide];
638
+    unsigned long long height = [screen pixelsHigh];
639
+    unsigned long long max = width * height;
640
+    unsigned long long red = 0, green = 0, blue = 0;
641
+    for (unsigned long long i = 0; i < max; i++) {
642
+        red += data[([screen samplesPerPixel] * i) + redC];
643
+        green += data[([screen samplesPerPixel] * i) + greenC];
644
+        blue += data[([screen samplesPerPixel] * i) + blueC];
645
+    }
646
+    
647
+    [self setLightsR:(red / max) G:(green / max) B:(blue / max)];
648
+}
649
+
532 650
 - (void)visualizeGPUUsage:(NSTimer *)timer {
533 651
     NSNumber *usage;
534 652
     NSNumber *freeVRAM;

+ 6
- 0
CaseLights/Base.lproj/MainMenu.xib View File

@@ -22,6 +22,8 @@
22 22
                 <outlet property="buttonOff" destination="gT4-tm-Hvn" id="EEv-lf-Qo7"/>
23 23
                 <outlet property="menuAnimations" destination="FJm-ND-abA" id="iGI-Hg-tms"/>
24 24
                 <outlet property="menuColors" destination="ZKz-ek-t6c" id="B2S-fy-Ca6"/>
25
+                <outlet property="menuDisplays" destination="Qgx-6D-9dl" id="MaT-c8-dLX"/>
26
+                <outlet property="menuItemDisplays" destination="1Cz-Xz-d16" id="rRz-n5-n2P"/>
25 27
                 <outlet property="menuPorts" destination="lFU-D0-M5p" id="pHd-AA-S3J"/>
26 28
                 <outlet property="menuVisualizations" destination="yZl-ae-7SI" id="b2b-dW-mvy"/>
27 29
                 <outlet property="statusMenu" destination="g7M-LS-DgA" id="txO-T1-8yD"/>
@@ -691,6 +693,10 @@
691 693
                     <modifierMask key="keyEquivalentModifierMask"/>
692 694
                     <menu key="submenu" title="Visualizations" id="yZl-ae-7SI"/>
693 695
                 </menuItem>
696
+                <menuItem title="Displays" hidden="YES" id="1Cz-Xz-d16">
697
+                    <modifierMask key="keyEquivalentModifierMask"/>
698
+                    <menu key="submenu" title="Displays" id="Qgx-6D-9dl"/>
699
+                </menuItem>
694 700
                 <menuItem title="Brightness" id="Oh0-1U-yDg">
695 701
                     <modifierMask key="keyEquivalentModifierMask"/>
696 702
                     <menu key="submenu" title="Brightness" id="jRu-sM-3xB">

+ 1
- 1
CaseLights/Info.plist View File

@@ -21,7 +21,7 @@
21 21
 	<key>CFBundleSignature</key>
22 22
 	<string>????</string>
23 23
 	<key>CFBundleVersion</key>
24
-	<string>118</string>
24
+	<string>132</string>
25 25
 	<key>LSApplicationCategoryType</key>
26 26
 	<string>public.app-category.utilities</string>
27 27
 	<key>LSMinimumSystemVersion</key>

+ 23
- 0
CaseLights/Screenshot.h View File

@@ -0,0 +1,23 @@
1
+//
2
+//  Screenshot.h
3
+//  CaseLights
4
+//
5
+//  Created by Thomas Buck on 27.12.15.
6
+//  Copyright © 2015 xythobuz. All rights reserved.
7
+//
8
+
9
+#import <Foundation/Foundation.h>
10
+
11
+@interface Screenshot : NSThread
12
+
13
+// Register and de-register the callback for configuration changes
14
++ (void)init:(AppDelegate *)appDelegate;
15
++ (void)close:(AppDelegate *)appDelegate;
16
+
17
+// List available displays. Returns an array of numbers.
18
++ (NSArray *)listDisplays;
19
+
20
++ (NSString *)displayNameFromDisplayID:(NSNumber *)displayID;
21
++ (NSBitmapImageRep *)screenshot:(NSNumber *)displayID;
22
+
23
+@end

+ 144
- 0
CaseLights/Screenshot.m View File

@@ -0,0 +1,144 @@
1
+//
2
+//  Screenshot.m
3
+//  CaseLights
4
+//
5
+//  Based on the Apple ScreenSnapshot example:
6
+//  https://developer.apple.com/library/mac/samplecode/ScreenSnapshot/Listings/ScreenSnapshot_ScreenSnapshotAppDelegate_m.html
7
+//
8
+//  Created by Thomas Buck on 27.12.15.
9
+//  Copyright © 2015 xythobuz. All rights reserved.
10
+//
11
+
12
+// Uncomment to store a screenshot for each display in the build directory
13
+//#define DEBUG_SCREENSHOT
14
+
15
+#import "AppDelegate.h"
16
+#import "Screenshot.h"
17
+
18
+static BOOL displayRegistrationCallBackSuccessful;
19
+
20
+static void displayRegisterReconfigurationCallback(CGDirectDisplayID display, CGDisplayChangeSummaryFlags flags, void *userInfo) {
21
+    AppDelegate *appDelegate = (__bridge AppDelegate*)userInfo;
22
+    static BOOL DisplayConfigurationChanged = NO;
23
+    
24
+    // Before display reconfiguration, this callback fires to inform
25
+    // applications of a pending configuration change. The callback runs
26
+    // once for each on-line display.  The flags passed in are set to
27
+    // kCGDisplayBeginConfigurationFlag.  This callback does not
28
+    // carry other per-display information, as details of how a
29
+    // reconfiguration affects a particular device rely on device-specific
30
+    // behaviors which may not be exposed by a device driver.
31
+    //
32
+    // After display reconfiguration, at the time the callback function
33
+    // is invoked, all display state reported by CoreGraphics, QuickDraw,
34
+    // and the Carbon Display Manager API will be up to date.  This callback
35
+    // runs after the Carbon Display Manager notification callbacks.
36
+    // The callback runs once for each added, removed, and currently
37
+    // on-line display.  Note that in the case of removed displays, calls into
38
+    // the CoreGraphics API with the removed display ID will fail.
39
+    
40
+    // Because the callback is called for each display I use DisplayConfigurationChanged to
41
+    // make sure we only disable the menu to change displays once and then refresh it only once.
42
+    if (flags == kCGDisplayBeginConfigurationFlag) {
43
+        if (DisplayConfigurationChanged == NO) {
44
+            [appDelegate clearDisplayUI];
45
+            DisplayConfigurationChanged = YES;
46
+        }
47
+    } else if (DisplayConfigurationChanged == YES) {
48
+        [appDelegate updateDisplayUI:[Screenshot listDisplays]];
49
+        DisplayConfigurationChanged = NO;
50
+    }
51
+}
52
+
53
+@implementation Screenshot
54
+
55
++ (void)init:(AppDelegate *)appDelegate {
56
+    // Applications who want to register for notifications of display changes would use
57
+    // CGDisplayRegisterReconfigurationCallback
58
+    //
59
+    // Display changes are reported via a callback mechanism.
60
+    //
61
+    // Callbacks are invoked when the app is listening for events,
62
+    // on the event processing thread, or from within the display
63
+    // reconfiguration function when in the program that is driving the
64
+    // reconfiguration.
65
+    displayRegistrationCallBackSuccessful = NO; // Hasn't been tried yet.
66
+    CGError err = CGDisplayRegisterReconfigurationCallback(displayRegisterReconfigurationCallback, (__bridge void * _Nullable)(appDelegate));
67
+    if (err == kCGErrorSuccess) {
68
+        displayRegistrationCallBackSuccessful = YES;
69
+    }
70
+}
71
+
72
++ (void)close:(AppDelegate *)appDelegate {
73
+    // CGDisplayRemoveReconfigurationCallback Removes the registration of a callback function that’s invoked
74
+    // whenever a local display is reconfigured.  We only remove the registration if it was successful in the first place.
75
+    if (displayRegistrationCallBackSuccessful == YES) {
76
+        CGDisplayRemoveReconfigurationCallback(displayRegisterReconfigurationCallback, (__bridge void * _Nullable)(appDelegate));
77
+    }
78
+}
79
+
80
++ (NSArray *)listDisplays {
81
+    CGDisplayCount dspCount = 0;
82
+    CGError err = CGGetActiveDisplayList(0, NULL, &dspCount);
83
+    if (err != CGDisplayNoErr) {
84
+        NSLog(@"Couldn't list any active displays (%d)!\n", err);
85
+        return nil;
86
+    }
87
+    
88
+    CGDirectDisplayID *displays = calloc((size_t)dspCount, sizeof(CGDirectDisplayID));
89
+    err = CGGetActiveDisplayList(dspCount, displays, &dspCount);
90
+    if (err != CGDisplayNoErr) {
91
+        NSLog(@"Couldn't get active display list (%d)!\n", err);
92
+        return nil;
93
+    }
94
+    
95
+    if (dspCount > 0) {
96
+        NSMutableArray *array = [NSMutableArray arrayWithCapacity:dspCount];
97
+        for (int i = 0; i < dspCount; i++) {
98
+            [array addObject:[NSNumber numberWithInt:displays[i]]];
99
+        }
100
+    
101
+        return [array copy];
102
+    } else {
103
+        NSLog(@"No displays found!\n");
104
+        return nil;
105
+    }
106
+}
107
+
108
++ (NSString *)displayNameFromDisplayID:(NSNumber *)displayID {
109
+    NSDictionary *displayInfo = CFBridgingRelease(IODisplayCreateInfoDictionary(CGDisplayIOServicePort([displayID unsignedIntValue]), kIODisplayOnlyPreferredName));
110
+    NSDictionary *localizedNames = [displayInfo objectForKey:[NSString stringWithUTF8String:kDisplayProductName]];
111
+    
112
+    // Use the first name
113
+    NSString *displayProductName = nil;
114
+    if ([localizedNames count] > 0) {
115
+        displayProductName = [localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]];
116
+        
117
+#ifdef DEBUG
118
+        NSLog(@"Display %u named \"%@\"!\n", [displayID unsignedIntValue], displayProductName);
119
+#endif
120
+    }
121
+    
122
+    return displayProductName;
123
+}
124
+
125
++ (NSBitmapImageRep *)screenshot:(NSNumber *)displayID {
126
+    CGImageRef image = CGDisplayCreateImage([displayID unsignedIntValue]);
127
+    NSBitmapImageRep *imgRep = [[NSBitmapImageRep alloc] initWithCGImage:image];
128
+    CFRelease(image);
129
+    
130
+#ifdef DEBUG_SCREENSHOT
131
+    NSData *data = [imgRep representationUsingType:NSPNGFileType properties:[NSDictionary dictionary]];
132
+    NSString *path = [NSString stringWithFormat:@"test-%u-%@.png", [displayID unsignedIntValue], [Screenshot displayNameFromDisplayID:displayID]];
133
+    NSError *error;
134
+    if ([data writeToFile:path options:0 error:&error] == YES) {
135
+        NSLog(@"Wrote debug image to \"%@\"\n", path);
136
+    } else {
137
+        NSLog(@"Error writing debug image to \"%@\": %@\n", path, error);
138
+    }
139
+#endif
140
+    
141
+    return imgRep;
142
+}
143
+
144
+@end

+ 10
- 0
CaseLights/Serial.m View File

@@ -21,6 +21,8 @@
21 21
 
22 22
 #import "Serial.h"
23 23
 
24
+#define MAX_SEND_ERRORS 10
25
+
24 26
 @interface Serial ()
25 27
 
26 28
 @property (assign) int fd;
@@ -157,11 +159,19 @@
157 159
     NSLog(@"Sending string %s", data);
158 160
 #endif
159 161
     
162
+    int errorCount = 0;
160 163
     ssize_t sent = 0;
161 164
     while (sent < length) {
162 165
         ssize_t ret = write(fd, data + sent, length - sent);
163 166
         if (ret < 0) {
164 167
             NSLog(@"Error writing to serial port: %s (%d)!\n", strerror(errno), errno);
168
+            errorCount++;
169
+            if (errorCount >= MAX_SEND_ERRORS) {
170
+#ifdef DEBUG
171
+                NSLog(@"Too many send errors! Giving up...\n");
172
+#endif
173
+                return;
174
+            }
165 175
         } else {
166 176
             sent += ret;
167 177
         }

Loading…
Cancel
Save