Skip to content

Implement a command line interface to show custom OSD#49

Draft
mkozjak wants to merge 4 commits intoalberti42:mainfrom
mkozjak:command-line
Draft

Implement a command line interface to show custom OSD#49
mkozjak wants to merge 4 commits intoalberti42:mainfrom
mkozjak:command-line

Conversation

@mkozjak
Copy link
Copy Markdown

@mkozjak mkozjak commented Mar 25, 2026

Summary by Sourcery

Add a configurable on-screen volume HUD position and a command-line entry point to trigger it.

New Features:

  • Introduce a CLI target that displays the Tahoe volume HUD based on command-line options for volume, title, and screen position.
  • Add a HUDPosition enum and API support to place the HUD in one of nine screen locations when not anchored to a status bar item.

Enhancements:

  • Allow TahoeVolumeHUD to optionally hide the app icon and reflow the title when no player icon is provided.
  • Adjust HUD positioning constants and glass tint to better align the HUD with screen edges and provide a fully transparent background on supported macOS versions.

This is proof-of-concept idea and is vibe-coded.

Build:

xcodebuild -project "Volume Control.xcodeproj" \
           -target "volume-control-osd" \
           -configuration Debug \
           CODE_SIGNING_ALLOWED=NO

Run:

./build/Debug/volume-control-osd.app/Contents/MacOS/volume-control-osd --title Bluesound --volume 30 --position top-right
Screenshot 2026-03-25 at 14 16 53

References #48.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Mar 25, 2026

Reviewer's Guide

Adds a CLI app that can invoke the Tahoe volume HUD with configurable volume, title, and on-screen position, extends the HUD API and layout to support optional status-bar anchoring, positional placement, and optional icons, and wires existing callers to the new interface.

Sequence diagram for CLI invocation of TahoeVolumeHUD

sequenceDiagram
    actor User
    participant Shell
    participant CLIProcess as volume_control_osd
    participant NSApp as NSApplication
    participant Delegate as CLIAppDelegate
    participant HUD as TahoeVolumeHUD

    User->>Shell: Run command with args
    Shell->>CLIProcess: Launch process

    CLIProcess->>CLIProcess: Parse --volume, --title, --position
    CLIProcess->>NSApp: [sharedApplication]
    CLIProcess->>NSApp: setActivationPolicy(Accessory)
    CLIProcess->>NSApp: set delegate = CLIAppDelegate
    CLIProcess->>NSApp: run()

    NSApp-->>Delegate: applicationDidFinishLaunching
    Delegate->>NSApp: activateIgnoringOtherApps(TRUE)
    Delegate->>Delegate: Create CLIPlayerApplication
    Delegate->>HUD: sharedManager()
    Delegate->>HUD: showHUDWithVolume(volume, usingMusicPlayer: player, andLabel: title, anchoredToStatusButton: nil, position: position)

    Note over HUD: HUD computes layout and position

    Delegate->>Delegate: Schedule NSTimer(kTotalLifetime)

    Delegate-->>NSApp: Timer fires
    Delegate->>NSApp: terminate(nil)
    NSApp-->>Shell: Process exits
Loading

File-Level Changes

Change Details Files
Extend TahoeVolumeHUD API and layout to support optional status button anchoring, configurable screen positions, and hiding the app icon when no player is provided.
  • Introduce HUDPosition NS_ENUM to represent a 3×3 grid of on-screen positions and make it part of the public TahoeVolumeHUD API.
  • Change showHUDWithVolume:... to accept nullable PlayerApplication and NSStatusBarButton along with a HUDPosition argument, updating callers accordingly.
  • Add Auto Layout constraints for title label leading with/without icon and an icon width constraint, toggling them at runtime based on whether an app icon is available.
  • Replace positionPanelBelowStatusButton: with positionPanel:position: to handle both status button-anchored placement and free placement on the selected screen position, using new kTopMargin and kSideMargin constants.
  • Adjust glass tint color to be fully transparent black (alpha 0) instead of opaque (alpha 1).
User interface/HUD/TahoeVolumeHUD.h
User interface/HUD/TahoeVolumeHUD.m
Sources/Controllers/AppDelegate.m
Introduce a CLI target that shows the TahoeVolumeHUD based on command-line arguments and exits after the HUD animation completes.
  • Add main_cli.m implementing a minimal NSApplication-based CLI that parses --volume, --title, and --position arguments, maps position strings to HUDPosition values, and validates inputs.
  • Create a lightweight CLIPlayerApplication stand-in exposing only the properties TahoeVolumeHUD requires, and use it when invoking the HUD from the CLI.
  • Drive the HUD lifecycle from CLIAppDelegate, activating the app, showing the HUD unanchored (nil status button) at the requested position, and terminating the app after a fixed timer covering fade-in, display, and fade-out.
  • Add an LSUIElement-style Info.plist under OSD/ so the CLI app runs as an agent-style app with NSApplication as principal class.
Sources/CLI/main_cli.m
OSD/Info.plist
Volume Control.xcodeproj/project.pbxproj

Possibly linked issues

  • #Run as a separate command-line app: They match: the PR delivers the requested standalone CLI OSD app reusing TahoeVolumeHUD without Volume-Control.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@mkozjak mkozjak marked this pull request as draft March 25, 2026 13:11
Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 2 issues, and left some high level feedback:

  • The CLI’s fixed kTotalLifetime duplicates internal HUD timing constants; consider exposing a completion callback or deriving this duration from TahoeVolumeHUD so the CLI doesn’t silently drift out of sync if the animation timings change.
  • In positionPanel:position: the position parameter is ignored when a status bar button is provided; if callers might expect HUDPosition to have an effect in both cases, either document that it’s ignored when anchored or adjust the implementation to reconcile the two behaviours.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The CLI’s fixed `kTotalLifetime` duplicates internal HUD timing constants; consider exposing a completion callback or deriving this duration from `TahoeVolumeHUD` so the CLI doesn’t silently drift out of sync if the animation timings change.
- In `positionPanel:position:` the `position` parameter is ignored when a status bar button is provided; if callers might expect `HUDPosition` to have an effect in both cases, either document that it’s ignored when anchored or adjust the implementation to reconcile the two behaviours.

## Individual Comments

### Comment 1
<location path="Sources/CLI/main_cli.m" line_range="41-42" />
<code_context>
+// fade-in + hold + fade-out cycle, then quits cleanly.
+// ---------------------------------------------------------------------------
+
+// Total lifetime = fade-in (0.25 s) + hold (1.5 s) + fade-out (0.45 s) + margin
+static const NSTimeInterval kTotalLifetime = 0.25 + 1.5 + 0.45 + 0.15;
+
+@interface CLIAppDelegate : NSObject <NSApplicationDelegate>
</code_context>
<issue_to_address>
**issue:** Avoid hard-coding HUD animation timings in the CLI to prevent drift if TahoeVolumeHUD timings change.

kTotalLifetime re-specifies the fade-in/hold/fade-out durations that already exist in TahoeVolumeHUD. If those HUD values change, the CLI timing will be wrong. Prefer either exposing a HUD API that returns its total lifecycle duration or computing this value from shared constants instead of duplicating the numbers here.
</issue_to_address>

### Comment 2
<location path="Sources/CLI/main_cli.m" line_range="58" />
<code_context>
+    // see through the window to the content behind it and renders opaque/solid.
+    [NSApp activateIgnoringOtherApps:YES];
+
+    // Show the HUD.  Passing nil for the status button → centers on screen.
+    CLIPlayerApplication *player = [[CLIPlayerApplication alloc] init];
+    player.currentVolume = self.volume;
</code_context>
<issue_to_address>
**nitpick:** The comment about HUD placement with a nil status button no longer matches the actual default behavior.

The behavior described here is outdated: with the default `position` of `HUDPositionTopCenter`, passing `nil` to `positionPanel:position:` shows the HUD near the top of the screen, not centered. Please update this to describe that `nil` uses the configured `HUDPosition` (currently `HUDPositionTopCenter`, placing the HUD below the menu bar) rather than always centering it.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +41 to +42
// Total lifetime = fade-in (0.25 s) + hold (1.5 s) + fade-out (0.45 s) + margin
static const NSTimeInterval kTotalLifetime = 0.25 + 1.5 + 0.45 + 0.15;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue: Avoid hard-coding HUD animation timings in the CLI to prevent drift if TahoeVolumeHUD timings change.

kTotalLifetime re-specifies the fade-in/hold/fade-out durations that already exist in TahoeVolumeHUD. If those HUD values change, the CLI timing will be wrong. Prefer either exposing a HUD API that returns its total lifecycle duration or computing this value from shared constants instead of duplicating the numbers here.

// see through the window to the content behind it and renders opaque/solid.
[NSApp activateIgnoringOtherApps:YES];

// Show the HUD. Passing nil for the status button → centers on screen.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nitpick: The comment about HUD placement with a nil status button no longer matches the actual default behavior.

The behavior described here is outdated: with the default position of HUDPositionTopCenter, passing nil to positionPanel:position: shows the HUD near the top of the screen, not centered. Please update this to describe that nil uses the configured HUDPosition (currently HUDPositionTopCenter, placing the HUD below the menu bar) rather than always centering it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant