Created
January 19, 2024 07:06
-
-
Save qbilius/5e6496fd00e6f12a8637e712ec969a11 to your computer and use it in GitHub Desktop.
Color indicator of an input source in MacOS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Color indicator of an input source in MacOS | |
| // Shows a dot at the top right corner that changes color depending on the current input source. | |
| // By default, it is set to show a green dot for the US keyboard layout and a red one for anything else. | |
| // You can show or hide it by pressing Cmd+Opt+Ctrl+Shift. | |
| // Why would one need such an indicator? | |
| // In order to have more screen space, I keep my menu bar hidden and only show up when I move mouse to the top of the screen. | |
| // Thus, I cannot see which input source is currently selected and often I find myself typing in non-US symbols | |
| // when in fact I want to use US layout, and vice versa. | |
| // This indicator removes this guesswork by providing me with a quick and non-intrusive way to determine the current layout. | |
| // Usage: | |
| // Dependencies: | |
| // - If using `clang` to compile: `xcode-select --install` | |
| // - If using `gcc` to compile: `brew install gcc` | |
| // Compile: | |
| // clang -framework Cocoa -o lang-dot lang-dot.m | |
| // or | |
| // gcc -framework Cocoa -o lang-dot lang-dot.m | |
| // Run: ./lang-dot | |
| // Automatically start upon reboot: | |
| // crontab -e | |
| // @reboot /path/to/lang-dot | |
| #import <Cocoa/Cocoa.h> | |
| NSString *targetLayout = @"com.apple.keylayout.US"; | |
| NSString *domain = @"com.apple.HIToolbox"; | |
| NSString *key = @"AppleCurrentKeyboardLayoutInputSourceID"; | |
| static BOOL isWindowVisible = YES; | |
| void checkAndUpdate(NSWindow *window) { | |
| NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:domain]; | |
| id keyboardLayout = [userDefaults objectForKey:key]; | |
| NSColor *backgroundColor = [keyboardLayout isEqualToString:targetLayout] ? [NSColor greenColor] : [NSColor redColor]; | |
| // Must run asynchronously as otherwise the color doesn't update | |
| dispatch_async(dispatch_get_main_queue(), ^{ | |
| [window setBackgroundColor:backgroundColor]; | |
| }); | |
| } | |
| int main(int argc, const char * argv[]) { | |
| @autoreleasepool { | |
| NSApplication *application = [NSApplication sharedApplication]; | |
| NSRect mainScreenFrame = [NSScreen mainScreen].frame; | |
| // Place it in the top right corner | |
| NSRect frame = NSMakeRect(mainScreenFrame.size.width - 3, | |
| mainScreenFrame.size.height - 3, | |
| 3, | |
| 3); | |
| NSWindow *window = [[NSWindow alloc] initWithContentRect:frame | |
| styleMask:NSWindowStyleMaskBorderless | |
| backing:NSBackingStoreBuffered | |
| defer:NO]; | |
| // Place the window above most other windows | |
| [window setLevel:NSFloatingWindowLevel + 1]; | |
| [window setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces]; | |
| [window makeKeyAndOrderFront:nil]; // Bring the window to the front | |
| // Detect Cmd+Opt+Ctrl+Shift even when the app is not focused | |
| [NSEvent addGlobalMonitorForEventsMatchingMask:NSEventMaskFlagsChanged handler:^(NSEvent *event) { | |
| unsigned int flags = [event modifierFlags]; | |
| bool cmdPressed = (flags & NSEventModifierFlagCommand) != 0; | |
| bool optPressed = (flags & NSEventModifierFlagOption) != 0; | |
| bool ctrlPressed = (flags & NSEventModifierFlagControl) != 0; | |
| bool shiftPressed = (flags & NSEventModifierFlagShift) != 0; | |
| if (cmdPressed && optPressed && ctrlPressed && shiftPressed) { | |
| isWindowVisible = !isWindowVisible; | |
| if (isWindowVisible) { | |
| [window orderFront:nil]; | |
| } else { | |
| [window orderOut:nil]; | |
| } | |
| } | |
| }]; | |
| // Use NSRunLoop to check and update every second | |
| NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 | |
| target:[NSBlockOperation blockOperationWithBlock:^{ | |
| checkAndUpdate(window); | |
| }] | |
| selector:@selector(main) | |
| userInfo:nil | |
| repeats:YES]; | |
| [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; | |
| [application run]; | |
| } | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment