Skip to content
2 changes: 1 addition & 1 deletion Switch/SWAccessibilityService.m
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ - (instancetype)init;
{
BailUnless(self = [super init], nil);

_haxQueue = dispatch_queue_create("foo", DISPATCH_QUEUE_SERIAL);
_haxQueue = dispatch_queue_create("haxQueue", DISPATCH_QUEUE_SERIAL);

return self;
}
Expand Down
67 changes: 49 additions & 18 deletions Switch/SWCoreWindowService.m
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,6 @@ - (instancetype)init;
[self.stateMachine incrementWithInvoke:false direction:SWIncrementDirectionDecreasing isRepeating:true];
}];

// update window group UI state with the state machine's updates of window groups
[RACObserve(self, stateMachine.windowList) subscribeNext:^(NSOrderedSet *windowList) {
@strongify(self);
[self.interface updateWindowList:windowList];
}];
// update the selected window group with the state machine's update of selector.selectedWindow
[RACObserve(self, stateMachine.selectedWindow) subscribeNext:^(SWWindow *window) {
@strongify(self);
Expand Down Expand Up @@ -257,6 +252,11 @@ - (void)stopService;
- (oneway void)windowListService:(SWWindowListService *)service updatedList:(NSOrderedSet *)windows;
{
[self.stateMachine updateWindowList:windows];

// Do not update the interface if the state machine has an action pending.
if (!self.stateMachine.pendingSwitch) {
[self.interface updateWindowList:self.stateMachine.windowList];
}
}

#pragma mark - SWStateMachineDelegate
Expand All @@ -278,22 +278,17 @@ - (void)stateMachine:(SWStateMachine *)stateMachine wantsWindowRaised:(SWWindow
if (!selectedWindow) {
return;
}

NSUInteger selectedIndex = [self.stateMachine.windowList indexOfObject:self.stateMachine.selectedWindow];

if (selectedIndex == 0 && [((SWWindow *)self.stateMachine.windowList.firstObject).application isActiveApplication]) {
[self.stateMachine cancelInvocation];
return;
}

[self.interface disableWindow:selectedWindow];

@weakify(self);
[[SWAccessibilityService sharedService] raiseWindow:selectedWindow completion:^(NSError *error) {
@strongify(self);
if (error) {
SWLog(@"Failed to raise window group %@: %@", selectedWindow, error);
} else if (self.stateMachine.pendingSwitch) {
// Sending a cancel invocation here is to work around #105
[self.stateMachine cancelInvocation];
Check(!self.stateMachine.wantsInterfaceVisible);
}

[self.interface enableWindow:selectedWindow];
}];
[self private_raiseWindow];
}

- (void)stateMachine:(SWStateMachine *)stateMachine wantsWindowClosed:(SWWindow *)window;
Expand Down Expand Up @@ -404,4 +399,40 @@ - (void)private_hideInterface;
[self.interface shouldShowInterface:false];
}

- (void)private_raiseWindow;
{
SWWindow *selectedWindow = self.stateMachine.selectedWindow;
if (!selectedWindow) { return; }

@weakify(self);
[[SWAccessibilityService sharedService] raiseWindow:selectedWindow completion:^(NSError *error) {
@strongify(self);
if (error) {
SWLog(@"Failed to raise window group %@: %@", selectedWindow, error);
}

NSOrderedSet *windowList = self.stateMachine.windowList;
NSUInteger selectedIndex = [windowList indexOfObject:selectedWindow];

if (selectedIndex == 0) {
// If the selected index was 0, this action won't change the window order so this code must replay the last update event.
[self.stateMachine updateWindowList:self.stateMachine.windowList];

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In an ideal world, this will break when #122 gets fixed, as activeApplication should become an immutable data property.

Unfortunately, refreshWindowList won't dispatch a notification if there is only a change in active application, so that'll have to happen later. Sorry, future me.

} else {
// Otherwise, force a window list reload.
[[SWWindowListService sharedService] refreshWindowList];
}

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
@strongify(self);
if (self.stateMachine.pendingSwitch) {
NSLog(@"Attempt to raise window failed, trying again...");
[self private_raiseWindow];
} else {
SWWindow *window = self.stateMachine.selectedWindow;
[self.interface enableWindow:window];
}
});
}];
}

@end
25 changes: 23 additions & 2 deletions Switch/SWSelector.m
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
//

#import "SWSelector.h"
#import "SWWindow.h"


@implementation SWSelector
Expand Down Expand Up @@ -140,6 +141,7 @@ - (instancetype)updateWithWindowList:(NSOrderedSet *)windowList;
{
// Carry over the current selected index by default.
NSInteger newSelectedIndex = self.selectedIndex;
NSUInteger selectedWindowIndex;

if (windowList && !windowList.count) {
// Empty set? Selected window not found.
Expand All @@ -149,9 +151,9 @@ - (instancetype)updateWithWindowList:(NSOrderedSet *)windowList;
} else if (self.windowList.count == 0) {
// Previously empty list? Select the beginning.
newSelectedIndex = 0;
} else if ([windowList containsObject:self.selectedWindow]) {
} else if ((selectedWindowIndex = [self private_indexOfWindow:self.selectedWindow inWindowList:windowList]) != NSNotFound) {
// Select the same window group as was previously selected, if it's still there.
newSelectedIndex = (NSInteger)[windowList indexOfObject:self.selectedWindow];
newSelectedIndex = (NSInteger)selectedWindowIndex;
} else if ((NSInteger)windowList.count <= newSelectedIndex) {
// Clamp the selected index to the end of the window list.
newSelectedIndex = (NSInteger)windowList.count - 1;
Expand All @@ -160,4 +162,23 @@ - (instancetype)updateWithWindowList:(NSOrderedSet *)windowList;
return [[[self class] alloc] initWithWindowList:windowList selectedIndex:newSelectedIndex];
}

- (NSUInteger)private_indexOfWindow:(id)selectedWindow inWindowList:(NSOrderedSet *)windowList;
{
if (selectedWindow == nil) {
return NSNotFound;
}
if (windowList == nil) {
return NSNotFound;
}

// Most of the unit tests for this class just shove NSNumbers into the collections instead of SWWindow objects
if (![selectedWindow respondsToSelector:NNTypedSelector1(SWWindow, isSameWindow:)]) {
return [windowList indexOfObject:selectedWindow];
}

return [windowList indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return [selectedWindow isSameWindow:obj];
}];
}

@end
10 changes: 5 additions & 5 deletions Switch/SWStateMachine.m
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ - (void)setActive:(_Bool)active;
}

self.selector = nil;
self.pendingSwitch = false;
}
}

Expand All @@ -182,8 +183,10 @@ - (void)setInvoked:(_Bool)invoked;

- (void)displayTimerCompleted;
{
StateLog(@"State machine display timer completed");
self.displayTimer = false;
if (!self.pendingSwitch) {
StateLog(@"State machine display timer completed");
self.displayTimer = false;
}
}

#pragma mark - Keyboard interactions
Expand Down Expand Up @@ -229,9 +232,6 @@ - (void)cancelInvocation;
{
StateLog(@"State machine event cancel invocation");

// Setting pendingSwitch here is to work around #105
self.pendingSwitch = false;

if (self.invoked) {
self.invoked = false;
}
Expand Down
1 change: 1 addition & 0 deletions Switch/SWWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ typedef NSPoint NNVec2;
- (NSScreen *)screen;
- (CGWindowID)windowID;

- (BOOL)isSameWindow:(SWWindow *)window;
- (BOOL)isRelatedToLowerWindow:(SWWindow *)window;
- (NNVec2)offsetOfCenterToCenterOfWindow:(SWWindow *)window;
- (NSSize)sizeDifferenceFromWindow:(SWWindow *)window;
Expand Down
13 changes: 13 additions & 0 deletions Switch/SWWindow.m
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,19 @@ - (BOOL)isEqual:(id)object;
return ([object isKindOfClass:[self class]] && [[self windowDescription] isEqual:[object windowDescription]]);
}

- (BOOL)isSameWindow:(SWWindow *)window;
{
if (window.windowID != self.windowID) {
return NO;
}

if (![window.application.name isEqualToString:self.application.name]) {
return NO;
}

return YES;
}

- (NSString *)description;
{
return [NSString stringWithFormat:@"%p <%u (%@)>", self, self.windowID, self.name];
Expand Down
10 changes: 9 additions & 1 deletion Switch/SWWindowContentsService.m
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ - (id)init;
BailUnless(self = [super init], nil);

_contentContainers = [NSMutableDictionary new];
_queue = dispatch_queue_create([[NSString stringWithFormat:@""] UTF8String], DISPATCH_QUEUE_SERIAL);
_queue = dispatch_queue_create([[NSString stringWithFormat:@"SWWindowContentsService"] UTF8String], DISPATCH_QUEUE_SERIAL);

[[NSNotificationCenter defaultCenter] addWeakObserver:self selector:NNSelfSelector1(private_windowUpdateNotification:) name:[SWWindowWorker notificationName] object:nil];

Expand Down Expand Up @@ -90,6 +90,8 @@ - (void)startService;
{
[super startService];

self->_contentContainers = [NSMutableDictionary new];

NSOrderedSet *windows = [SWWindowListService sharedService].windows;
if (windows) {
[self windowListService:nil updatedList:windows];
Expand All @@ -102,6 +104,7 @@ - (void)stopService;
{
dispatch_async(self.queue, ^{
[self.contentContainers removeAllObjects];
self->_contentContainers = nil;
});

[[NNServiceManager sharedManager] removeObserver:self forService:[SWWindowListService class]];
Expand Down Expand Up @@ -155,6 +158,11 @@ - (oneway void)windowListService:(SWWindowListService *)service updatedList:(NSO
});
}

- (oneway void)windowListServiceStopped:(SWWindowListService *)service;
{
[self windowListService:nil updatedList:[NSOrderedSet orderedSet]];
}

#pragma mark - Internal

- (void)private_windowUpdateNotification:(NSNotification *)notification;
Expand Down
2 changes: 2 additions & 0 deletions Switch/SWWindowListService.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,6 @@

@property (nonatomic, copy, readonly) NSOrderedSet *windows;

- (void)refreshWindowList;

@end
6 changes: 5 additions & 1 deletion Switch/SWWindowListService.m
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,11 @@ + (NSOrderedSet *)sortedWindowGroups:(NSOrderedSet *)windowGroups;
return result;
}

- (void)refreshWindowList;
{
[self.worker refreshWindowList];
}

#pragma mark - Internal

- (void)private_workerUpdatedWindowList:(NSNotification *)notification;
Expand All @@ -233,7 +238,6 @@ - (void)private_updateWindowList:(NSArray *)windowInfoList;
NSOrderedSet *windowGroupList = [[self class] filterWindowObjectsToWindowGroups:windowObjectList];
NSOrderedSet *sortedWindowGroupList = [[self class] sortedWindowGroups:windowGroupList];

// This probably needs to change for #105 to get fixed properly as well—there are two views of equality, window order and window order and activation.
if (![self.windows isEqualToOrderedSet:sortedWindowGroupList]) {
self.windows = sortedWindowGroupList;
[(id<SWWindowListSubscriber>)self.subscriberDispatcher windowListService:self updatedList:self.windows];
Expand Down
2 changes: 2 additions & 0 deletions Switch/SWWindowListWorker.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@

@interface SWWindowListWorker : NNPollingObject

- (void)refreshWindowList;

@end
7 changes: 7 additions & 0 deletions Switch/SWWindowListWorker.m
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ - (instancetype)init;
#pragma mark - NNPollingObject

- (oneway void)main;
{
[self refreshWindowList];
}

#pragma mark - SWWindowListWorker

- (void)refreshWindowList;
{
CFArrayRef cgWindowInfoList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID);
NSArray *windowInfoList = CFBridgingRelease(cgWindowInfoList);
Expand Down
16 changes: 14 additions & 2 deletions Switch/SWWindowWorker.m
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ @interface SWWindowWorker ()

@property (nonatomic, copy, readonly) SWWindow *window;

@property (nonatomic, assign) _Bool firstUpdate;
@property (nonatomic, strong) NSImage *previousCapture;

@end
Expand All @@ -43,7 +44,9 @@ - (instancetype)initWithModelObject:(SWWindow *)window;
if (!(self = [super initWithQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)])) { return nil; }

_window = window;
self.interval = NNPollingIntervalFast;
self.interval = NNPollingIntervalSlow;

_firstUpdate = true;

return self;
}
Expand Down Expand Up @@ -85,7 +88,12 @@ - (oneway void)main;
if (!imageChanged) {
self.interval = MIN(NNPollingIntervalSlow, self.interval * 2.0);
} else {
self.interval = NNPollingIntervalFast;
if (self.firstUpdate) {
self.interval = NNPollingIntervalSlow;
} else {
self.interval = NNPollingIntervalFast;
}

self.previousCapture = image;

[self postNotification:@{
Expand All @@ -100,6 +108,10 @@ - (oneway void)main;
// Window does not exist. Stop the worker loop.
self.interval = -1.0;
}

if (self.firstUpdate) {
self.firstUpdate = false;
}
}

#pragma mark - SWWindowWorker
Expand Down
Loading