diff --git a/README.md b/README.md index 395b2de..446f97d 100644 --- a/README.md +++ b/README.md @@ -10,15 +10,19 @@ macOS 10.10 Yosemite or higher version. ## Installation -Download the [latest stable version](https://github.com/CocoaHeadsBrasil/MuteUnmuteMic/releases/download/1.4.2/Un.MuteMic.zip) or clone this repo and build & run. +Download or clone this repo and build & run. + +To build as a macos app: In XCode: choose _Product_ -> _Archive_ -> Click _Distribute App_ button -> Choose _Copy App_ -> Choose the export folder and click _Export_ ## Usage -![[Un]MuteMic usage](https://cloud.githubusercontent.com/assets/235208/10419593/143171fc-704a-11e5-8270-374ca898685b.gif) +Use the shortcut key, click the icon, or open the menu and click _Toggle Mute_. ## Authors -CocoaHeads Brasil: [http://www.cocoaheads.com.br/](http://www.cocoaheads.com.br/) +* CocoaHeads Brasil: [http://www.cocoaheads.com.br/](http://www.cocoaheads.com.br/) +* [racecarparts](https://github.com/racecarparts) +* Shoutout to: [https://github.com/jaz303/JFHotkeyManager](https://github.com/jaz303/JFHotkeyManager) ## License diff --git a/[Un]MuteMic.xcodeproj/project.pbxproj b/[Un]MuteMic.xcodeproj/project.pbxproj index 9e672a6..35be54a 100644 --- a/[Un]MuteMic.xcodeproj/project.pbxproj +++ b/[Un]MuteMic.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ D9A9F66F1BC35EDC004395E8 /* AudioMixer.c in Sources */ = {isa = PBXBuildFile; fileRef = D9A9F66D1BC35EDC004395E8 /* AudioMixer.c */; }; + ECD9888D2448C7B2000626C4 /* JFHotkeyManager.m in Sources */ = {isa = PBXBuildFile; fileRef = ECD9888C2448C7B2000626C4 /* JFHotkeyManager.m */; }; ECF208BE1BAF2DE6000D3C2C /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = ECF208BD1BAF2DE6000D3C2C /* AppDelegate.m */; }; ECF208C11BAF2DE6000D3C2C /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = ECF208C01BAF2DE6000D3C2C /* main.m */; }; ECF208C31BAF2DE6000D3C2C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = ECF208C21BAF2DE6000D3C2C /* Assets.xcassets */; }; @@ -17,6 +18,8 @@ /* Begin PBXFileReference section */ D9A9F66D1BC35EDC004395E8 /* AudioMixer.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = AudioMixer.c; path = "[Un]MuteMic/AudioMixer.c"; sourceTree = ""; }; D9A9F66E1BC35EDC004395E8 /* AudioMixer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AudioMixer.h; path = "[Un]MuteMic/AudioMixer.h"; sourceTree = ""; }; + ECD9888B2448C785000626C4 /* JFHotkeyManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JFHotkeyManager.h; sourceTree = ""; }; + ECD9888C2448C7B2000626C4 /* JFHotkeyManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JFHotkeyManager.m; sourceTree = ""; }; ECF208B91BAF2DE6000D3C2C /* [Un]MuteMic.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "[Un]MuteMic.app"; sourceTree = BUILT_PRODUCTS_DIR; }; ECF208BC1BAF2DE6000D3C2C /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; ECF208BD1BAF2DE6000D3C2C /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; @@ -73,6 +76,8 @@ ECF208C71BAF2DE6000D3C2C /* Info.plist */, ECF208BF1BAF2DE6000D3C2C /* Supporting Files */, D9A9F6701BC5830F004395E8 /* AudioMixer */, + ECD9888B2448C785000626C4 /* JFHotkeyManager.h */, + ECD9888C2448C7B2000626C4 /* JFHotkeyManager.m */, ); path = "[Un]MuteMic"; sourceTree = ""; @@ -116,6 +121,7 @@ TargetAttributes = { ECF208B81BAF2DE6000D3C2C = { CreatedOnToolsVersion = 7.0; + LastSwiftMigration = 1140; }; }; }; @@ -124,6 +130,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, Base, ); @@ -157,6 +164,7 @@ ECF208C11BAF2DE6000D3C2C /* main.m in Sources */, ECF208BE1BAF2DE6000D3C2C /* AppDelegate.m in Sources */, D9A9F66F1BC35EDC004395E8 /* AudioMixer.c in Sources */, + ECD9888D2448C7B2000626C4 /* JFHotkeyManager.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -257,11 +265,15 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = "[Un]MuteMic/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = br.com.cocoaheads.MuteUnmuteMic; PRODUCT_NAME = "[Un]MuteMic"; + SWIFT_OBJC_BRIDGING_HEADER = "[Un]MuteMic-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -269,11 +281,14 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = "[Un]MuteMic/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = br.com.cocoaheads.MuteUnmuteMic; PRODUCT_NAME = "[Un]MuteMic"; + SWIFT_OBJC_BRIDGING_HEADER = "[Un]MuteMic-Bridging-Header.h"; + SWIFT_VERSION = 5.0; }; name = Release; }; diff --git a/[Un]MuteMic.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/[Un]MuteMic.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 061567d..1d10b82 100644 --- a/[Un]MuteMic.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/[Un]MuteMic.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:/Users/tom.wheeler/dev/MuteUnmuteMic/[Un]MuteMic.xcodeproj"> diff --git a/[Un]MuteMic.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/[Un]MuteMic.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/[Un]MuteMic.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/[Un]MuteMic.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/[Un]MuteMic.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/[Un]MuteMic.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/[Un]MuteMic/AppDelegate.m b/[Un]MuteMic/AppDelegate.m index cb833e2..e250c55 100644 --- a/[Un]MuteMic/AppDelegate.m +++ b/[Un]MuteMic/AppDelegate.m @@ -7,14 +7,18 @@ #import "AppDelegate.h" #import "AudioMixer.h" +#import +#import "JFHotkeyManager.h" -static NSInteger const kDefaultVolume = 70; +static NSInteger const kDefaultVolume = 80; @interface AppDelegate () @property (nonatomic) NSStatusItem *menuItem; @property (nonatomic) BOOL muted; @property (nonatomic) NSInteger inputVolumeToUnmute; +@property JFHotkeyManager *hkm; +//@property __JFHotkey *shortcut; @end @@ -25,6 +29,13 @@ static NSInteger const kDefaultVolume = 70; [self initDefaults]; [self configureStatusBar]; [self updateInputVolume]; + + // Initialise a new hotkey manager + _hkm = [[JFHotkeyManager alloc] init]; + + // Bind the hotkey by key code reference number and modifiers: + // want modifiers? use `withModifiers:cmdKey + optionKey + shiftKey` + JFHotKeyRef hk = [_hkm bindKeyRef:80 withModifiers:0 target:self action:@selector(toggleMute)]; } - (void)initDefaults @@ -39,7 +50,7 @@ static NSInteger const kDefaultVolume = 70; NSStatusItem *menuItem = [statusBar statusItemWithLength:NSVariableStatusItemLength]; - [menuItem setToolTip:@"[Un]MuteMic by CocoaHeads Brazil"]; + [menuItem setToolTip:@"i haz teh mutez"]; [menuItem setImage:[NSImage imageNamed:@"mic_on"]]; [menuItem setHighlightMode:YES]; @@ -61,6 +72,11 @@ static NSInteger const kDefaultVolume = 70; } } +- (IBAction)didToggleMute:(NSMenuItem *)sender +{ + [self toggleMute]; +} + - (void)toggleMute { self.muted = !self.muted; @@ -96,11 +112,14 @@ static NSInteger const kDefaultVolume = 70; SetHardwareMute(muted); // set image - self.menuItem.image = [NSImage imageNamed:imageName]; + self.menuItem.button.image = [NSImage imageNamed:imageName]; } - (void)showMenu { +// NSMenuItem *mi = [self.menu itemWithTitle:@"Shortcut"]; + + [self.menuItem popUpStatusItemMenu:self.menu]; } @@ -115,4 +134,15 @@ static NSInteger const kDefaultVolume = 70; [self updateInputVolume]; } +- (IBAction)didCallSetShortcut:(NSMenuItem *)sender +{ + NSMenuItem *menuItem = (NSMenuItem*) sender; + NSString *menuString = menuItem.title; + + if ([menuString isEqualToString:@"Shortcut"]) + { + NSLog(@"%@", menuString); + } +} + @end diff --git a/[Un]MuteMic/Assets.xcassets/mic_off.imageset/Contents.json b/[Un]MuteMic/Assets.xcassets/mic_off.imageset/Contents.json index 3ba7a8b..f79b0fe 100644 --- a/[Un]MuteMic/Assets.xcassets/mic_off.imageset/Contents.json +++ b/[Un]MuteMic/Assets.xcassets/mic_off.imageset/Contents.json @@ -1,21 +1,21 @@ { "images" : [ { - "idiom" : "mac", "filename" : "mic_off.png", + "idiom" : "mac", "scale" : "1x" }, { - "idiom" : "mac", "filename" : "mic_off@2x.png", + "idiom" : "mac", "scale" : "2x" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 }, "properties" : { - "template-rendering-intent" : "template" + "template-rendering-intent" : "original" } -} \ No newline at end of file +} diff --git a/[Un]MuteMic/Assets.xcassets/mic_off.imageset/mic_off.png b/[Un]MuteMic/Assets.xcassets/mic_off.imageset/mic_off.png index a831717..09e727f 100644 Binary files a/[Un]MuteMic/Assets.xcassets/mic_off.imageset/mic_off.png and b/[Un]MuteMic/Assets.xcassets/mic_off.imageset/mic_off.png differ diff --git a/[Un]MuteMic/Assets.xcassets/mic_off.imageset/mic_off@2x.png b/[Un]MuteMic/Assets.xcassets/mic_off.imageset/mic_off@2x.png index 61362f0..2577355 100644 Binary files a/[Un]MuteMic/Assets.xcassets/mic_off.imageset/mic_off@2x.png and b/[Un]MuteMic/Assets.xcassets/mic_off.imageset/mic_off@2x.png differ diff --git a/[Un]MuteMic/Assets.xcassets/mic_on.imageset/mic_on.png b/[Un]MuteMic/Assets.xcassets/mic_on.imageset/mic_on.png index 67c0991..8e06698 100644 Binary files a/[Un]MuteMic/Assets.xcassets/mic_on.imageset/mic_on.png and b/[Un]MuteMic/Assets.xcassets/mic_on.imageset/mic_on.png differ diff --git a/[Un]MuteMic/Assets.xcassets/mic_on.imageset/mic_on@2x.png b/[Un]MuteMic/Assets.xcassets/mic_on.imageset/mic_on@2x.png index a16e19d..fab8d4b 100644 Binary files a/[Un]MuteMic/Assets.xcassets/mic_on.imageset/mic_on@2x.png and b/[Un]MuteMic/Assets.xcassets/mic_on.imageset/mic_on@2x.png differ diff --git a/[Un]MuteMic/Base.lproj/MainMenu.xib b/[Un]MuteMic/Base.lproj/MainMenu.xib index e3ae2d5..1e05113 100644 --- a/[Un]MuteMic/Base.lproj/MainMenu.xib +++ b/[Un]MuteMic/Base.lproj/MainMenu.xib @@ -1,7 +1,8 @@ - - + + - + + @@ -19,6 +20,12 @@ + + + + + + @@ -35,13 +42,13 @@ - + - + @@ -73,7 +80,7 @@ - + diff --git a/[Un]MuteMic/JFHotkeyManager.h b/[Un]MuteMic/JFHotkeyManager.h new file mode 100644 index 0000000..019471a --- /dev/null +++ b/[Un]MuteMic/JFHotkeyManager.h @@ -0,0 +1,50 @@ +// +// JFHotkeyManager.h +// JFHotkeyManager +// +// Created by Jason Frame on 23/02/2010. +// Copyright 2010 Jason Frame. All rights reserved. +// + +#import +#import + +typedef NSUInteger JFHotKeyRef; + +@interface JFHotkeyManager : NSObject { + + NSMutableDictionary *_hotkeys; + UInt32 _nextId; + +} + +- (JFHotKeyRef)bind:(NSString *)commandString + target:(id)target + action:(SEL)selector; + +- (JFHotKeyRef)bindKeyRef:(UInt32)keyRef + withModifiers:(UInt32)modifiers + target:(id)target + action:(SEL)selector; + +- (void)unbind:(JFHotKeyRef)keyRef; + +- (void)_dispatch:(NSUInteger)hotkeyId; + +@end + +@interface __JFHotkey : NSObject { + id _target; + SEL _action; + EventHotKeyRef _ref; +} + +- (id)initWithHotkeyID:(UInt32)hotkeyID + keyRef:(UInt32)keyRef + modifiers:(UInt32)modifiers + target:(id)target + action:(SEL)selector; + +- (void)invoke; + +@end diff --git a/[Un]MuteMic/JFHotkeyManager.m b/[Un]MuteMic/JFHotkeyManager.m new file mode 100644 index 0000000..63227d9 --- /dev/null +++ b/[Un]MuteMic/JFHotkeyManager.m @@ -0,0 +1,252 @@ +// +// JFHotkeyManager.m +// JFHotkeyManager +// +// Created by Jason Frame on 23/02/2010. +// Copyright 2010 Jason Frame. All rights reserved. +// + +#import "JFHotkeyManager.h" + +static OSStatus hotkeyHandler(EventHandlerCallRef hnd, + EventRef evt, + void *data) { + + EventHotKeyID hkID; + GetEventParameter(evt, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(hkID), NULL, &hkID); + + JFHotkeyManager *hkm = (__bridge JFHotkeyManager *)data; + [hkm _dispatch:hkID.id]; + + return noErr; + +} + +static NSMutableDictionary *keyMap; +static NSMutableDictionary *modMap; + +static void mapKey(NSString *s, NSUInteger key) { + keyMap[s] = @(key); +} + +static void mapMod(NSString *s, NSUInteger mod) { + modMap[s] = @(mod); +} + + +@implementation __JFHotkey +- (id)initWithHotkeyID:(UInt32)hotkeyID + keyRef:(UInt32)keyRef + modifiers:(UInt32)modifiers + target:(id)target + action:(SEL)selector { + if (self = [super init]) { + + EventHotKeyID kID; + kID.id = kID.signature = hotkeyID; + + RegisterEventHotKey(keyRef, modifiers, kID, GetApplicationEventTarget(), 0, &_ref); + + _target = target; + _action = selector; + + } + return self; +} + +- (void)dealloc { + UnregisterEventHotKey(_ref); +} + +- (void)invoke { + IMP imp = [_target methodForSelector:_action]; + void (*func)(id, SEL) = (void *)imp; + func(_target, _action); +} +@end + + +@implementation JFHotkeyManager + ++ (void)initialize { + + modMap = [[NSMutableDictionary alloc] init]; + keyMap = [[NSMutableDictionary alloc] init]; + + // Is this guaranteed to be the same across systems/keyboards? + // Probably not. + + // + // Keys + + mapKey(@"a", 0x00); + mapKey(@"b", 0x0B); + mapKey(@"c", 0x09); + mapKey(@"d", 0x02); + mapKey(@"e", 0x0E); + mapKey(@"f", 0x03); + mapKey(@"g", 0x05); + mapKey(@"h", 0x04); + mapKey(@"i", 0x22); + mapKey(@"j", 0x26); + mapKey(@"k", 0x29); + mapKey(@"l", 0x25); + mapKey(@"m", 0x2E); + mapKey(@"n", 0x2D); + mapKey(@"o", 0x1F); + mapKey(@"p", 0x23); + mapKey(@"q", 0x0C); + mapKey(@"r", 0x0F); + mapKey(@"s", 0x01); + mapKey(@"t", 0x11); + mapKey(@"u", 0x20); + mapKey(@"v", 0x09); + mapKey(@"w", 0x0D); + mapKey(@"x", 0x07); + mapKey(@"y", 0x10); + mapKey(@"z", 0x06); + + mapKey(@"0", 0x1D); + mapKey(@"1", 0x12); + mapKey(@"2", 0x13); + mapKey(@"3", 0x14); + mapKey(@"4", 0x15); + mapKey(@"5", 0x17); + mapKey(@"6", 0x16); + mapKey(@"7", 0x1A); + mapKey(@"8", 0x1C); + mapKey(@"9", 0x19); + + mapKey(@",", 0x2B); + mapKey(@".", 0x2F); + mapKey(@"/", 0x2C); + + mapKey(@"f1", 0x7A); + mapKey(@"f2", 0x79); + mapKey(@"f3", 0x63); + mapKey(@"f4", 0x76); + mapKey(@"f5", 0x60); + mapKey(@"f6", 0x61); + mapKey(@"f7", 0x62); + mapKey(@"f8", 0x64); + mapKey(@"f9", 0x65); + mapKey(@"f10", 0x6D); + mapKey(@"f11", 0x67); + mapKey(@"f12", 0x6F); + mapKey(@"f13", 0x69); + mapKey(@"f14", 0x6B); + mapKey(@"f15", 0x71); + + mapKey(@"escape", 0x35); + mapKey(@"esc", 0x35); + mapKey(@"space", 0x31); + mapKey(@"enter", 0x24); + mapKey(@"tab", 0x30); + mapKey(@"backspace",0x33); + mapKey(@"bkspc", 0x33); + + mapKey(@"left", 0x7B); + mapKey(@"right", 0x7C); + mapKey(@"down", 0x7D); + mapKey(@"up", 0x7E); + + // + // Modifiers + + mapMod(@"apple", cmdKey); + + mapMod(@"ctrl", controlKey); + mapMod(@"ctl", controlKey); + mapMod(@"control", controlKey); + + mapMod(@"opt", optionKey); + mapMod(@"option", optionKey); + mapMod(@"alt", optionKey); + + mapMod(@"cmd", cmdKey); + mapMod(@"command", cmdKey); + mapMod(@"apple", cmdKey); + + mapMod(@"shift", shiftKey); + +} + +- (id)init { + if (self = [super init]) { + + _hotkeys = [[NSMutableDictionary alloc] init]; + _nextId = 1; + + EventTypeSpec evtType; + evtType.eventClass = kEventClassKeyboard; + evtType.eventKind = kEventHotKeyPressed; + InstallApplicationEventHandler(&hotkeyHandler, + 1, + &evtType, + (__bridge void*)self, + NULL); + + } + return self; +} + +- (JFHotKeyRef)bind:(NSString *)commandString + target:(id)target + action:(SEL)selector { + + UInt32 keyRef = 0; + UInt32 modifiers = 0; + + NSArray *bits = [[commandString lowercaseString] componentsSeparatedByString:@" "]; + for (NSString *bit in bits) { + + NSNumber *code = modMap[bit]; + if (code != nil) { + modifiers += [code unsignedIntValue]; + continue; + } + + code = keyMap[bit]; + if (code != nil) { + keyRef = [code unsignedIntValue]; + continue; + } + + } + + return [self bindKeyRef:keyRef + withModifiers:modifiers + target:target + action:selector]; + +} + +- (JFHotKeyRef)bindKeyRef:(UInt32)keyRef + withModifiers:(UInt32)modifiers + target:(id)target + action:(SEL)selector { + + UInt32 keyID = _nextId; + _nextId++; + + __JFHotkey *hk = [[__JFHotkey alloc] initWithHotkeyID:keyID + keyRef:keyRef + modifiers:modifiers + target:target + action:selector]; + + _hotkeys[@(keyID)] = hk; + + return keyID; + +} + +- (void)unbind:(JFHotKeyRef)keyRef { + [_hotkeys removeObjectForKey:@(keyRef)]; +} + +- (void)_dispatch:(NSUInteger)hotkeyId { + [_hotkeys[@(hotkeyId)] invoke]; +} + +@end