TeamsControl v0.5.0

A Hammerspoon Spoon that toggles the Microsoft Teams meeting microphone from a single hotkey, from any app.

Overview

It sends Cmd+Shift+M, then confirms the toggle actually registered by reading Teams' "Mute mic"/"Unmute mic" accessibility button label before and after -- a real success/failure signal instead of a timeout guess -- and shows the resulting state (or the failure) in an on-screen alert.

The accessibility-tree button lookup (findButton in ui.lua) is adapted from _teamsFindButtonByLabel in RobvH/teams-mac-hotkeys.

The Teams local API client (api.lua) follows the protocol handling of asp55/MSTeams.spoon2 (MIT, (c) 2026 asp55).

Download: https://github.com/hugoh/TeamsControl.spoon/releases/latest

Variables

TeamsControl.teamsBundleID Variable #

Bundle identifier of the Microsoft Teams app (default: "com.microsoft.teams2").

TeamsControl.activationTimeout Variable #

Seconds to wait for Teams to come to the front for the click fallback before giving up (default: 5).

TeamsControl.clickSettleDelay Variable #

Seconds between accessibility re-checks after sending the mute keystroke (default: 0.05).

TeamsControl.clickSettleMaxRetries Variable #

How many times to re-check the button label -- after the keystroke, and again after the click fallback -- before declaring the toggle failed (default: 10).

TeamsControl.showMenubar Variable #

Show a menu bar indicator during Teams calls: the macOS mic glyph when unmuted, the slashed mic when muted, nothing otherwise. Clicking it toggles mute (default: true).

TeamsControl.useApi Variable #

Use Teams' local third-party API (a websocket on localhost) when it is available: mute toggles then need no keystroke or focus change. Falls back to the keystroke/click path whenever the API is off, not paired, or doesn't answer (default: true).

TeamsControl.apiPort Variable #

Port of Teams' local API (default: 8124).

TeamsControl.apiRetryInterval Variable #

Seconds between attempts to reach Teams' local API while it is unavailable (default: 30).

TeamsControl.apiConfirmTimeout Variable #

Seconds to wait for Teams to confirm a mute toggle sent through its API before falling back to the keystroke (default: 0.5).

TeamsControl.apiMaxRetries Variable #

How many times to retry reaching Teams' local API for one running instance of Teams before giving up until Teams restarts. A successful connection resets the count (default: 3).

Methods

TeamsControl:configure(opts) Method #

Sets one or more of TeamsControl's variables from a table. Call it before start().

  • opts - a table with any of the variable names documented on this page as keys
  • The TeamsControl object, for method chaining
TeamsControl:toggleMute() Method #

Toggles the Teams meeting microphone by sending Cmd+Shift+M to Teams without bringing it forward. If that doesn't register, Teams is activated, its mute button clicked, and focus returned to the app you were in. Re-entrant calls while a toggle is already in flight are ignored.

  • done - an optional function called once when the toggle has settled (or the call was ignored), so callers can drive a busy indicator
  • The TeamsControl object, for method chaining
TeamsControl:bindHotkeys(mapping) Method #

Binds hotkeys for TeamsControl.

  • mapping - a table with a toggleMute key mapped to a { {modifiers}, key } spec
  • The TeamsControl object, for method chaining
TeamsControl:init() Method #

Called automatically by hs.loadSpoon(). Logs the loaded version.

  • The TeamsControl object, for method chaining
TeamsControl:start() Method #

Watches for Teams running. While it does, starts the menu bar indicator, if showMenubar is set, and connects to Teams' local API, if useApi is set.

  • The TeamsControl object, for method chaining
TeamsControl:stop() Method #

Unbinds any hotkeys bound via bindHotkeys, stops the menu bar indicator and disconnects from the Teams API.

  • The TeamsControl object, for method chaining