From 9765193c1fb42d28bbfc1d881424daf554e999a6 Mon Sep 17 00:00:00 2001 From: Marcus Wu Date: Fri, 19 Mar 2021 20:29:36 -0400 Subject: [PATCH] Added app volume and pan setting to AppleScript --- BGMApp/BGMApp.xcodeproj/project.pbxproj | 6 ++ BGMApp/BGMApp/BGMAppDelegate.h | 2 + BGMApp/BGMApp/BGMAppDelegate.mm | 2 +- BGMApp/BGMApp/BGMAppVolumes.h | 3 + BGMApp/BGMApp/BGMAppVolumes.m | 73 +++++++++++++++++++ BGMApp/BGMApp/BGMAppVolumesController.h | 8 ++ BGMApp/BGMApp/BGMAppVolumesController.mm | 19 +++-- BGMApp/BGMApp/Scripting/BGMASApplication.h | 31 ++++++++ BGMApp/BGMApp/Scripting/BGMASApplication.m | 69 ++++++++++++++++++ BGMApp/BGMApp/Scripting/BGMApp.sdef | 54 +++++++++++++- .../Scripting/BGMAppDelegate+AppleScript.h | 3 + .../Scripting/BGMAppDelegate+AppleScript.mm | 26 ++++++- 12 files changed, 285 insertions(+), 11 deletions(-) create mode 100644 BGMApp/BGMApp/Scripting/BGMASApplication.h create mode 100644 BGMApp/BGMApp/Scripting/BGMASApplication.m diff --git a/BGMApp/BGMApp.xcodeproj/project.pbxproj b/BGMApp/BGMApp.xcodeproj/project.pbxproj index 4dfb4a4d..2983973f 100644 --- a/BGMApp/BGMApp.xcodeproj/project.pbxproj +++ b/BGMApp/BGMApp.xcodeproj/project.pbxproj @@ -225,6 +225,7 @@ 27FB8C2F1DE468320084DB9D /* BGM_Utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 27FB8C2E1DE468320084DB9D /* BGM_Utils.cpp */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGM_Utils.cpp"; }; }; 27FB8C301DE4758A0084DB9D /* BGMPlayThrough.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962E51BC94E91008A4DF7 /* BGMPlayThrough.cpp */; }; 27FB8C311DE4758A0084DB9D /* BGM_Utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 27FB8C2E1DE468320084DB9D /* BGM_Utils.cpp */; }; + 9E129A412602AE620005851B /* BGMASApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 9E129A402602AE620005851B /* BGMASApplication.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -433,6 +434,8 @@ 27F7D48F1D2483B100821C4B /* BGMDecibel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BGMDecibel.m; path = "Music Players/BGMDecibel.m"; sourceTree = ""; }; 27F7D4911D2484A300821C4B /* Decibel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Decibel.h; path = "Music Players/Decibel.h"; sourceTree = ""; }; 27FB8C2E1DE468320084DB9D /* BGM_Utils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = BGM_Utils.cpp; path = ../SharedSource/BGM_Utils.cpp; sourceTree = ""; }; + 9E129A3F2602AE620005851B /* BGMASApplication.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BGMASApplication.h; path = Scripting/BGMASApplication.h; sourceTree = ""; }; + 9E129A402602AE620005851B /* BGMASApplication.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BGMASApplication.m; path = Scripting/BGMASApplication.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -562,6 +565,8 @@ 1C2FC31D1EC723A100A76592 /* BGMASOutputDevice.h */, 1C2FC31A1EC7238A00A76592 /* BGMASOutputDevice.mm */, 1C2FC2FF1EB4D6E700A76592 /* BGMApp.sdef */, + 9E129A3F2602AE620005851B /* BGMASApplication.h */, + 9E129A402602AE620005851B /* BGMASApplication.m */, ); name = Scripting; sourceTree = ""; @@ -1152,6 +1157,7 @@ 1C837DD81F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm in Sources */, 1C9258472090287F00B8D3A6 /* BGMGooglePlayMusicDesktopPlayerConnection.m in Sources */, 1C1963011BCAC0F6008A4DF7 /* CACFString.cpp in Sources */, + 9E129A412602AE620005851B /* BGMASApplication.m in Sources */, 1C1962E71BC94E91008A4DF7 /* BGMPlayThrough.cpp in Sources */, 1C8D8304204238DB00A838F2 /* BGMSwinsian.m in Sources */, 1C1962FA1BCAC061008A4DF7 /* CADebugMacros.cpp in Sources */, diff --git a/BGMApp/BGMApp/BGMAppDelegate.h b/BGMApp/BGMApp/BGMAppDelegate.h index a2166c6c..1a162cd7 100644 --- a/BGMApp/BGMApp/BGMAppDelegate.h +++ b/BGMApp/BGMApp/BGMAppDelegate.h @@ -24,6 +24,7 @@ // Local Includes #import "BGMAudioDeviceManager.h" +#import "BGMAppVolumesController.h" // System Includes #import @@ -53,6 +54,7 @@ static NSInteger const kSeparatorBelowVolumesMenuItemTag = 4; @property (weak) IBOutlet NSMenuItem* debugLoggingMenuItemUnwrapped; @property (readonly) BGMAudioDeviceManager* audioDevices; +@property BGMAppVolumesController* appVolumes; @end diff --git a/BGMApp/BGMApp/BGMAppDelegate.mm b/BGMApp/BGMApp/BGMAppDelegate.mm index e754c9ae..e6470265 100644 --- a/BGMApp/BGMApp/BGMAppDelegate.mm +++ b/BGMApp/BGMApp/BGMAppDelegate.mm @@ -64,7 +64,6 @@ @implementation BGMAppDelegate { BGMAutoPauseMenuItem* autoPauseMenuItem; BGMMusicPlayers* musicPlayers; BGMSystemSoundsVolume* systemSoundsVolume; - BGMAppVolumesController* appVolumes; BGMOutputDeviceMenuSection* outputDeviceMenuSection; BGMPreferencesMenu* prefsMenu; BGMDebugLoggingMenuItem* debugLoggingMenuItem; @@ -73,6 +72,7 @@ @implementation BGMAppDelegate { } @synthesize audioDevices = audioDevices; +@synthesize appVolumes = appVolumes; - (void) awakeFromNib { [super awakeFromNib]; diff --git a/BGMApp/BGMApp/BGMAppVolumes.h b/BGMApp/BGMApp/BGMAppVolumes.h index f18f8af9..3946d9e9 100644 --- a/BGMApp/BGMApp/BGMAppVolumes.h +++ b/BGMApp/BGMApp/BGMAppVolumes.h @@ -44,6 +44,9 @@ - (void) removeAllAppVolumeMenuItems; +- (BGMAppVolumeAndPan) getVolumeAndPanForApp:(NSRunningApplication*)app; +- (void) setVolumeAndPan:(BGMAppVolumeAndPan)volumeAndPan forApp:(NSRunningApplication*)app; + @end // Protocol for the UI custom classes diff --git a/BGMApp/BGMApp/BGMAppVolumes.m b/BGMApp/BGMApp/BGMAppVolumes.m index d10802fd..b376e8ca 100644 --- a/BGMApp/BGMApp/BGMAppVolumes.m +++ b/BGMApp/BGMApp/BGMAppVolumes.m @@ -124,6 +124,79 @@ - (void) insertMenuItemForApp:(NSRunningApplication*)app } } +- (NSMenuItem*) getMenuItemForApp:(NSRunningApplication*)app { + NSInteger lastAppVolumeMenuItemIndex = [self lastMenuItemIndex] - 2; + + for (NSInteger i = [self firstMenuItemIndex]; i <= lastAppVolumeMenuItemIndex; i++) { + NSMenuItem* item = [bgmMenu itemAtIndex:i]; + NSRunningApplication* itemApp = item.representedObject; + BGMAssert(itemApp, "!itemApp for %s", item.title.UTF8String); + + if ([itemApp isEqual:app]) { + return item; + } + } + for (NSInteger i = 0; i < [moreAppsMenu numberOfItems]; i++) { + NSMenuItem* item = [moreAppsMenu itemAtIndex:i]; + NSRunningApplication* itemApp = item.representedObject; + BGMAssert(itemApp, "!itemApp for %s", item.title.UTF8String); + + if ([itemApp isEqual:app]) { + return item; + } + } + + return nil; +} + +- (BGMAppVolumeAndPan) getVolumeAndPanForApp:(NSRunningApplication*)app { + BGMAppVolumeAndPan result = { + .volume = -1, + .pan = -1 + }; + + NSMenuItem *item = [self getMenuItemForApp:app]; + + if (item == nil) { + return result; + } + + for (NSView* subview in item.view.subviews) { + // Get the volume. + if ([subview isKindOfClass:[BGMAVM_VolumeSlider class]]) { + result.volume = [(BGMAVM_VolumeSlider*)subview intValue]; + } + + // Get the pan position. + if ([subview isKindOfClass:[BGMAVM_PanSlider class]]) { + result.pan = [(BGMAVM_PanSlider*)subview intValue]; + } + } + + return result; +} + +- (void) setVolumeAndPan:(BGMAppVolumeAndPan)volumeAndPan forApp:(NSRunningApplication*)app { + NSMenuItem *item = [self getMenuItemForApp:app]; + + if (item == nil) { + return; + } + + for (NSView* subview in item.view.subviews) { + // Get the volume. + if (volumeAndPan.volume != -1 && [subview isKindOfClass:[BGMAVM_VolumeSlider class]]) { + [(BGMAVM_VolumeSlider*)subview setRelativeVolume:volumeAndPan.volume]; + } + + // Get the pan position. + if (volumeAndPan.pan != -1 && [subview isKindOfClass:[BGMAVM_PanSlider class]]) { + [(BGMAVM_PanSlider*)subview setPanPosition:volumeAndPan.pan]; + } + } + +} + // Create a blank menu item to copy as a template. - (NSMenuItem*) createBlankAppVolumeMenuItem { NSMenuItem* menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; diff --git a/BGMApp/BGMApp/BGMAppVolumesController.h b/BGMApp/BGMApp/BGMAppVolumesController.h index 24b0337d..2daeaaae 100644 --- a/BGMApp/BGMApp/BGMAppVolumesController.h +++ b/BGMApp/BGMApp/BGMAppVolumesController.h @@ -29,6 +29,11 @@ #pragma clang assume_nonnull begin +typedef struct BGMAppVolumeAndPan { + int volume; + int pan; +} BGMAppVolumeAndPan; + @interface BGMAppVolumesController : NSObject - (id) initWithMenu:(NSMenu*)menu @@ -45,6 +50,9 @@ forAppWithProcessID:(pid_t)processID forAppWithProcessID:(pid_t)processID bundleID:(NSString* __nullable)bundleID; +- (BGMAppVolumeAndPan) getVolumeAndPanForApp:(NSRunningApplication *)app; +- (void) setVolumeAndPan:(BGMAppVolumeAndPan)volumeAndPan forApp:(NSRunningApplication*)app; + @end #pragma clang assume_nonnull end diff --git a/BGMApp/BGMApp/BGMAppVolumesController.mm b/BGMApp/BGMApp/BGMAppVolumesController.mm index 5a1c05a0..dc34a7bc 100644 --- a/BGMApp/BGMApp/BGMAppVolumesController.mm +++ b/BGMApp/BGMApp/BGMAppVolumesController.mm @@ -40,11 +40,6 @@ #pragma clang assume_nonnull begin -typedef struct BGMAppVolumeAndPan { - int volume; - int pan; -} BGMAppVolumeAndPan; - @implementation BGMAppVolumesController { // The App Volumes UI. BGMAppVolumes* appVolumes; @@ -104,6 +99,20 @@ - (void) insertMenuItemsForApps:(NSArray*)apps { } } +- (BGMAppVolumeAndPan) getVolumeAndPanForApp:(NSRunningApplication *)app { + return [appVolumes getVolumeAndPanForApp:app]; +} + +- (void) setVolumeAndPan:(BGMAppVolumeAndPan)volumeAndPan forApp:(NSRunningApplication*)app { + [appVolumes setVolumeAndPan:volumeAndPan forApp:app]; + if (volumeAndPan.volume != -1) { + [self setVolume:volumeAndPan.volume forAppWithProcessID:app.processIdentifier bundleID:app.bundleIdentifier]; + } + if (volumeAndPan.pan != -1) { + [self setPanPosition:volumeAndPan.pan forAppWithProcessID:app.processIdentifier bundleID:app.bundleIdentifier]; + } +} + - (BGMAppVolumeAndPan) getVolumeAndPanForApp:(NSRunningApplication*)app fromVolumes:(const CACFArray&)volumes { BGMAppVolumeAndPan volumeAndPan = { diff --git a/BGMApp/BGMApp/Scripting/BGMASApplication.h b/BGMApp/BGMApp/Scripting/BGMASApplication.h new file mode 100644 index 00000000..3c7c961c --- /dev/null +++ b/BGMApp/BGMApp/Scripting/BGMASApplication.h @@ -0,0 +1,31 @@ +// +// BGMASApplication.h +// Background Music +// +// Created by Marcus Wu on 3/17/21. +// Copyright © 2021 Background Music contributors. All rights reserved. +// + +// Local Includes +#import "BGMAppVolumesController.h" + +// System Includes +#import +#import + + +NS_ASSUME_NONNULL_BEGIN + +@interface BGMASApplication : NSObject + +- (instancetype) initWithApplication:(NSRunningApplication*)app + volumeController:(BGMAppVolumesController*)volumeController + parentSpecifier:(NSScriptObjectSpecifier* __nullable)parentSpecifier + index:(int)i; + +@property (readonly) NSString* name; +@property int volume; +@property int pan; +@end + +NS_ASSUME_NONNULL_END diff --git a/BGMApp/BGMApp/Scripting/BGMASApplication.m b/BGMApp/BGMApp/Scripting/BGMASApplication.m new file mode 100644 index 00000000..549b09fd --- /dev/null +++ b/BGMApp/BGMApp/Scripting/BGMASApplication.m @@ -0,0 +1,69 @@ +// +// BGMASApplication.m +// Background Music +// +// Created by Marcus Wu on 3/17/21. +// Copyright © 2021 Background Music contributors. All rights reserved. +// + +// Self Include +#import "BGMASApplication.h" + +@implementation BGMASApplication { + NSScriptObjectSpecifier* parentSpecifier; + NSRunningApplication *application; + BGMAppVolumesController* appVolumesController; + int index; +} + +- (instancetype) initWithApplication:(NSRunningApplication*)app + volumeController:(BGMAppVolumesController*)volumeController + parentSpecifier:(NSScriptObjectSpecifier* __nullable)parent + index:(int)i { + if ((self = [super init])) { + parentSpecifier = parent; + application = app; + appVolumesController = volumeController; + index = i; + } + + return self; +} + +- (NSString*) name { + return [NSString stringWithFormat:@"%@", [application localizedName]]; +} + +- (int) volume { + return [appVolumesController getVolumeAndPanForApp:application].volume; +} + +- (void) setVolume:(int)vol { + BGMAppVolumeAndPan volume = { + .volume = vol, + .pan = -1 + }; + [appVolumesController setVolumeAndPan:volume forApp:application]; +} + +- (int) pan { + return [appVolumesController getVolumeAndPanForApp:application].pan; +} + +- (void) setPan:(int)pan { + BGMAppVolumeAndPan thePan = { + .volume = -1, + .pan = pan + }; + [appVolumesController setVolumeAndPan:thePan forApp:application]; +} + +- (NSScriptObjectSpecifier* __nullable) objectSpecifier { + NSScriptClassDescription* parentClassDescription = [parentSpecifier keyClassDescription]; + return [[NSNameSpecifier alloc] initWithContainerClassDescription:parentClassDescription + containerSpecifier:parentSpecifier + key:@"applications" + name:self.name]; +} + +@end diff --git a/BGMApp/BGMApp/Scripting/BGMApp.sdef b/BGMApp/BGMApp/Scripting/BGMApp.sdef index 81ddb43a..df91891a 100644 --- a/BGMApp/BGMApp/Scripting/BGMApp.sdef +++ b/BGMApp/BGMApp/Scripting/BGMApp.sdef @@ -9,8 +9,7 @@ + plural="output devices"> @@ -19,14 +18,49 @@ code="pnam" description="The name of the output device." type="text" - access="r"/> + access="r"> + + - + + + + + + + + + + + + + + + + + + + @@ -48,11 +82,23 @@ + + + + + + + + diff --git a/BGMApp/BGMApp/Scripting/BGMAppDelegate+AppleScript.h b/BGMApp/BGMApp/Scripting/BGMAppDelegate+AppleScript.h index b25fcf50..9a5d5aed 100644 --- a/BGMApp/BGMApp/Scripting/BGMAppDelegate+AppleScript.h +++ b/BGMApp/BGMApp/Scripting/BGMAppDelegate+AppleScript.h @@ -24,6 +24,7 @@ // Local Includes #import "BGMASOutputDevice.h" +#import "BGMASApplication.h" // System Includes #import @@ -37,6 +38,8 @@ @property BGMASOutputDevice* selectedOutputDevice; @property (readonly) NSArray* outputDevices; +@property double mainVolume; +@property (readonly) NSArray* applications; @end diff --git a/BGMApp/BGMApp/Scripting/BGMAppDelegate+AppleScript.mm b/BGMApp/BGMApp/Scripting/BGMAppDelegate+AppleScript.mm index 927dcd41..f2560c84 100644 --- a/BGMApp/BGMApp/Scripting/BGMAppDelegate+AppleScript.mm +++ b/BGMApp/BGMApp/Scripting/BGMAppDelegate+AppleScript.mm @@ -30,6 +30,7 @@ #import "CAHALAudioSystemObject.h" #import "CAAutoDisposer.h" +const AudioObjectPropertyScope kScope = kAudioDevicePropertyScopeOutput; #pragma clang assume_nonnull begin @@ -43,7 +44,7 @@ - (BOOL) application:(NSApplication*)sender delegateHandlesKey:(NSString*)key { [key UTF8String]); } - return [@[@"selectedOutputDevice", @"outputDevices"] containsObject:key]; + return [@[@"selectedOutputDevice", @"outputDevices", @"mainVolume", @"applications"] containsObject:key]; } - (BGMASOutputDevice*) selectedOutputDevice { @@ -83,6 +84,29 @@ - (void) setSelectedOutputDevice:(BGMASOutputDevice*)device { return outputDevices; } +- (double) mainVolume { + BGMAudioDevice bgmDevice = [self.audioDevices bgmDevice]; + return bgmDevice.GetVolumeControlScalarValue(kScope, kMasterChannel); +} + +- (void) setMainVolume:(double)mainVolume { + BGMAudioDevice bgmDevice = [self.audioDevices bgmDevice]; + bgmDevice.SetMasterVolumeScalar(kScope, (Float32)mainVolume); +} + +- (NSArray*) applications { + NSArray* apps = [[NSWorkspace sharedWorkspace] runningApplications]; + NSMutableArray* applications = [NSMutableArray arrayWithCapacity:[apps count]]; + + for (UInt32 i = 0; i < [apps count]; i++) { + BGMASApplication *app = [[BGMASApplication alloc] initWithApplication:apps[i] volumeController:self.appVolumes parentSpecifier:[self objectSpecifier] index:i]; + + [applications addObject:app]; + } + + return applications; +} + @end #pragma clang assume_nonnull end