|
17 | 17 | // BGMAppVolumes.m |
18 | 18 | // BGMApp |
19 | 19 | // |
20 | | -// Copyright © 2016-2020 Kyle Neideck |
| 20 | +// Copyright © 2016-2020, 2026 Kyle Neideck |
21 | 21 | // Copyright © 2017 Andrew Tonner |
22 | 22 | // Copyright © 2021 Marcus Wu |
23 | 23 | // Copyright © 2022 Jon Egan |
| 24 | +// Copyright © 2026 TwelfthFace |
24 | 25 | // |
25 | 26 |
|
26 | 27 | // Self Include |
@@ -423,20 +424,132 @@ - (void) setUpWithApp:(NSRunningApplication*)app |
423 | 424 |
|
424 | 425 | @end |
425 | 426 |
|
| 427 | +@implementation BGMAVM_VolumeMute { |
| 428 | + pid_t appProcessID; |
| 429 | + NSString* __nullable appBundleID; |
| 430 | + BGMAppVolumesController* controller; |
| 431 | +} |
| 432 | + |
| 433 | +- (NSString*) lastNonZeroVolumeDefaultsKey { |
| 434 | + if (appBundleID.length > 0) { |
| 435 | + return [NSString stringWithFormat:@"BGMAVM_LastNonZeroVolume_%@", appBundleID]; |
| 436 | + } |
| 437 | + return [NSString stringWithFormat:@"BGMAVM_LastNonZeroVolume_pid_%d", appProcessID]; |
| 438 | +} |
| 439 | + |
| 440 | +- (BOOL) isMuted:(int)value { |
| 441 | + return value <= kAppRelativeVolumeMinRawValue; |
| 442 | +} |
| 443 | + |
| 444 | +- (int) defaultRestoreVolume { |
| 445 | + return (int)((kAppRelativeVolumeMaxRawValue + kAppRelativeVolumeMinRawValue) / 2); |
| 446 | +} |
| 447 | + |
| 448 | +- (BGMAVM_VolumeSlider* __nullable) findSiblingVolumeSlider { |
| 449 | + for (NSView* view in self.superview.subviews) { |
| 450 | + if ([view isKindOfClass:[BGMAVM_VolumeSlider class]]) { |
| 451 | + return (BGMAVM_VolumeSlider*)view; |
| 452 | + } |
| 453 | + } |
| 454 | + return nil; |
| 455 | +} |
| 456 | + |
| 457 | +- (void) updateButtonForVolume:(int)volume { |
| 458 | + BOOL muted = [self isMuted:volume]; |
| 459 | + |
| 460 | + if ([NSImage respondsToSelector:@selector(imageWithSystemSymbolName:accessibilityDescription:)]) { |
| 461 | +#pragma clang diagnostic push |
| 462 | +#pragma clang diagnostic ignored "-Wpartial-availability" |
| 463 | + NSString* symbol = muted ? @"speaker.slash.fill" : @"speaker.wave.2.fill"; |
| 464 | + NSString* description = muted ? @"Unmute" : @"Mute"; |
| 465 | + self.image = [NSImage imageWithSystemSymbolName:symbol accessibilityDescription:description]; |
| 466 | +#pragma clang diagnostic pop |
| 467 | + self.imagePosition = NSImageOnly; |
| 468 | + self.title = @""; |
| 469 | + } else { |
| 470 | + self.title = muted ? @"Unmute" : @"Mute"; |
| 471 | + } |
| 472 | +} |
| 473 | + |
| 474 | +- (void) bgm_syncForVolume:(int)volume { |
| 475 | + [self updateButtonForVolume:volume]; |
| 476 | +} |
| 477 | + |
| 478 | +- (void) setUpWithApp:(NSRunningApplication*)app |
| 479 | + context:(BGMAppVolumes*)ctx |
| 480 | + controller:(BGMAppVolumesController*)ctrl |
| 481 | + menuItem:(NSMenuItem*)menuItem { |
| 482 | +#pragma unused (ctx, menuItem) |
| 483 | + |
| 484 | + controller = ctrl; |
| 485 | + appProcessID = app.processIdentifier; |
| 486 | + appBundleID = app.bundleIdentifier; |
| 487 | + |
| 488 | + self.target = self; |
| 489 | + self.action = @selector(mutePressed:); |
| 490 | + |
| 491 | + BGMAVM_VolumeSlider* slider = [self findSiblingVolumeSlider]; |
| 492 | + int currentVol = slider ? slider.intValue : kAppRelativeVolumeMinRawValue; |
| 493 | + [self updateButtonForVolume:currentVol]; |
| 494 | +} |
| 495 | + |
| 496 | +- (IBAction) mutePressed:(id)sender { |
| 497 | +#pragma unused(sender) |
| 498 | + |
| 499 | + BGMAVM_VolumeSlider* slider = [self findSiblingVolumeSlider]; |
| 500 | + if (!slider) { |
| 501 | + DebugMsg("Mute button: no slider found"); |
| 502 | + return; |
| 503 | + } |
| 504 | + |
| 505 | + int currentVol = slider.intValue; |
| 506 | + BOOL mutedNow = [self isMuted:currentVol]; |
| 507 | + |
| 508 | + if (!mutedNow) { |
| 509 | + // Store last volume |
| 510 | + [[NSUserDefaults standardUserDefaults] setInteger:currentVol |
| 511 | + forKey:[self lastNonZeroVolumeDefaultsKey]]; |
| 512 | + |
| 513 | + [slider setRelativeVolume:kAppRelativeVolumeMinRawValue]; |
| 514 | + } else { |
| 515 | + NSInteger last = [[NSUserDefaults standardUserDefaults] integerForKey:[self lastNonZeroVolumeDefaultsKey]]; |
| 516 | + int restoreVol = (int)last; |
| 517 | + |
| 518 | + if (restoreVol <= kAppRelativeVolumeMinRawValue || |
| 519 | + restoreVol > kAppRelativeVolumeMaxRawValue) { |
| 520 | + restoreVol = [self defaultRestoreVolume]; |
| 521 | + } |
| 522 | + |
| 523 | + [slider setRelativeVolume:restoreVol]; |
| 524 | + } |
| 525 | + |
| 526 | + [controller setVolume:slider.intValue |
| 527 | + forAppWithProcessID:appProcessID |
| 528 | + bundleID:appBundleID]; |
| 529 | + |
| 530 | + [self updateButtonForVolume:slider.intValue]; |
| 531 | +} |
| 532 | + |
| 533 | +@end |
| 534 | + |
426 | 535 | @implementation BGMAVM_VolumeSlider { |
427 | 536 | // Will be set to -1 for apps without a pid |
428 | 537 | pid_t appProcessID; |
429 | 538 | NSString* __nullable appBundleID; |
430 | 539 | BGMAppVolumesController* controller; |
| 540 | + |
| 541 | + // Keep the menu item so we can sync the mute button when the slider changes. |
| 542 | + __weak NSMenuItem* menuItem; |
431 | 543 | } |
432 | 544 |
|
433 | 545 | - (void) setUpWithApp:(NSRunningApplication*)app |
434 | 546 | context:(BGMAppVolumes*)ctx |
435 | 547 | controller:(BGMAppVolumesController*)ctrl |
436 | | - menuItem:(NSMenuItem*)menuItem { |
437 | | - #pragma unused (ctx, menuItem) |
| 548 | + menuItem:(NSMenuItem*)inMenuItem { |
| 549 | + #pragma unused (ctx) |
438 | 550 |
|
439 | 551 | controller = ctrl; |
| 552 | + menuItem = inMenuItem; |
440 | 553 |
|
441 | 554 | self.target = self; |
442 | 555 | self.action = @selector(appVolumeChanged); |
@@ -483,6 +596,13 @@ - (void) appVolumeChanged { |
483 | 596 | // The values from our sliders are in |
484 | 597 | // [kAppRelativeVolumeMinRawValue, kAppRelativeVolumeMaxRawValue] already. |
485 | 598 | [controller setVolume:self.intValue forAppWithProcessID:appProcessID bundleID:appBundleID]; |
| 599 | + |
| 600 | + // Sync the mute button so it reflects muted/unmuted when the user drags the slider. |
| 601 | + for (NSView* subview in menuItem.view.subviews) { |
| 602 | + if ([subview isKindOfClass:[BGMAVM_VolumeMute class]]) { |
| 603 | + [(BGMAVM_VolumeMute*)subview bgm_syncForVolume:self.intValue]; |
| 604 | + } |
| 605 | + } |
486 | 606 | } |
487 | 607 |
|
488 | 608 | @end |
@@ -533,3 +653,4 @@ - (void) appPanPositionChanged { |
533 | 653 | } |
534 | 654 |
|
535 | 655 | @end |
| 656 | + |
0 commit comments