Screen Lock Media Privacy Investigation
Issue: #2015 Investigation Date: 2025-12-12 Status: Research Complete
Executive Summary
This document investigates the feasibility of implementing automatic camera and microphone disabling when the user's screen is locked - a privacy feature that exists in the Windows Teams client. The investigation covers screen lock detection capabilities, media device control mechanisms, and implementation approaches with a Linux-first philosophy.
Key Finding: This feature is feasible using a user-script approach that aligns with Linux/Unix philosophy. Rather than implementing all screen lock detection logic in the application, Teams for Linux should expose commands/APIs that users can invoke from their own screen lock scripts (D-Bus listeners, systemd hooks, etc.).
Problem Statement
Users want camera and microphone to be automatically disabled when their screen locks to prevent unintended audio/video transmission when stepping away from their computer. This is a privacy-focused feature that already exists in the Windows Microsoft Teams client.
Use Cases
- User steps away during a meeting and locks their screen
- Screen auto-locks due to inactivity during a call
- Privacy protection when leaving workstation unattended
Research Findings
1. Screen Lock Detection
Electron PowerMonitor API
Electron's powerMonitor module provides system power and screen lock event monitoring:
Available Events:
lock-screen- Emitted when the system is about to lock the screenunlock-screen- Emitted when the system screen is unlocked
Platform Support:
- ✅ macOS - Full support
- ✅ Windows - Full support
- ❌ Linux - NOT currently supported (as of Electron 32+)
Code Example:
const { powerMonitor } = require('electron');
powerMonitor.on('lock-screen', () => {
console.log('Screen locked - disable media devices');
});
powerMonitor.on('unlock-screen', () => {
console.log('Screen unlocked');
});
Alternative: System Idle State
The powerMonitor.getSystemIdleState() method can return system state including "locked":
const state = powerMonitor.getSystemIdleState(60); // Returns: "active", "idle", "locked", or "unknown"
This is already used in the app's IdleMonitor class (app/idle/monitor.js).
Limitations:
- Polling-based (not event-driven)
- May not work consistently across all Linux desktop environments
- Less precise than dedicated lock/unlock events
Linux-Specific Solutions
Since Electron doesn't support lock-screen/unlock-screen events on Linux, alternative approaches are needed:
Option 1: D-Bus Integration
Linux desktop environments expose screen lock events via D-Bus:
- GNOME/Ubuntu:
org.gnome.ScreenSaverinterface - KDE Plasma:
org.freedesktop.ScreenSaverinterface - Cinnamon:
org.cinnamon.ScreenSaverinterface
Example D-Bus monitoring:
dbus-monitor --session "type='signal',interface='org.gnome.ScreenSaver'"
Implementation approach:
- Use Node.js D-Bus library (e.g.,
dbus-nextordbus-native) - Listen to
ActiveChangedsignal - Handle different desktop environments
Option 2: Polling with getSystemIdleState
Less elegant but cross-platform compatible:
- Poll
powerMonitor.getSystemIdleState()periodically - React when state changes to/from "locked"
- Already have infrastructure in
IdleMonitorclass
Option 3: Wait for Electron Support
Track Electron Issue #38088:
- Feature request filed April 2023
- Still open as of August 2024
- Assigned to maintainer Charles Kerr
- Reference implementation suggested (KeePassXC's D-Bus approach)
2. Media Device Control
Current Implementation Patterns
The app already has patterns for media device interception in app/browser/tools/disableAutogain.js:
Intercepts getUserMedia calls:
// Patch modern getUserMedia API
patchFunction(navigator.mediaDevices, "getUserMedia", function (original) {
return function getUserMedia(constraints) {
// Modify constraints here
return original.call(this, constraints);
};
});
This demonstrates we can:
- Hook into media device requests
- Modify constraints before they're applied
- Track when media streams are created
Media Stream Control
To disable active camera/microphone:
Option 1: Stop Media Tracks
When screen locks, iterate through active media streams and stop tracks:
// Get all video elements (Teams uses these for calls)
const mediaElements = document.querySelectorAll('video');
mediaElements.forEach(video => {
const stream = video.srcObject;
if (stream instanceof MediaStream) {
stream.getTracks().forEach(track => {
if (track.kind === 'video' || track.kind === 'audio') {
track.stop();
console.log(`Stopped ${track.kind} track due to screen lock`);
}
});
}
});
Option 2: Block getUserMedia During Lock
Prevent new media streams while locked:
let isScreenLocked = false;
navigator.mediaDevices.getUserMedia = new Proxy(originalGetUserMedia, {
apply(target, thisArg, args) {
if (isScreenLocked) {
console.warn('Media access blocked: screen is locked');
return Promise.reject(new DOMException('Screen is locked', 'NotAllowedError'));
}
return Reflect.apply(target, thisArg, args);
}
});
Option 3: Track and Restore
More sophisticated approach:
- Track which tracks were active before lock
- Stop tracks on lock
- Optionally offer to restore on unlock (user preference)
Teams-Specific Considerations
- Teams manages its own media streams through React components
- Stopping tracks may trigger Teams UI to show camera/mic as disabled
- Teams might attempt to re-request permissions when unlocked
- Need to test behavior with screen sharing (is that also disabled?)
3. Existing Codebase Integration Points
Relevant Modules
IdleMonitor (app/idle/monitor.js):
- Already uses
powerMonitor.getSystemIdleState() - Tracks user idle status for Teams presence
- Could be extended to handle lock state
BrowserWindowManager (app/mainAppWindow/browserWindowManager.js):
- Manages screen lock inhibition during calls (opposite use case!)
- Already has IPC handlers for call state
- Uses wake lock to prevent screen from sleeping
Browser Tools (app/browser/tools/):
disableAutogain.js- Shows pattern for intercepting getUserMediawakeLock.js- Shows pattern for power management features- Would add new tool:
mediaPrivacy.jsorscreenLockMediaControl.js
IPC Communication Pattern
Need bidirectional communication:
- Main → Renderer: Notify when screen locks/unlocks
- Renderer → Main: Report media stream state changes
// Main process (powerMonitor listener)
powerMonitor.on('lock-screen', () => {
mainWindow.webContents.send('screen-locked');
});
// Renderer process (browser tool)
ipcRenderer.on('screen-locked', () => {
disableActiveMediaDevices();
});
Linux-First Approach: User-Controlled Scripts
Philosophy
Teams for Linux is a Linux application that should embrace Unix philosophy: provide composable tools that users can wire together. Rather than trying to detect every desktop environment's screen lock mechanism, we should:
- Expose commands/APIs that users can call to disable/enable media
- Document integration patterns for common desktop environments
- Let users hook into their OS using the tools they prefer
Existing Infrastructure to Leverage
Teams for Linux already has two mechanisms that users can integrate with:
1. MQTT Commands (Already Implemented!)
The MQTT integration already supports bidirectional communication:
Existing commands:
toggle-mute- Toggle microphonetoggle-video- Toggle cameratoggle-hand-raise- Raise/lower hand
How it works:
# User's screen lock script can send MQTT commands
mosquitto_pub -h localhost -t "teams/command" \
-m '{"action":"toggle-video"}' -q 1
Limitation: These are toggles, not explicit disable/enable commands. We need to add:
disable-media- Explicitly disable camera and micenable-media- Explicitly enable camera and mic (or restore previous state)query-media-state- Get current camera/mic state
2. Similar Pattern: incomingCallCommand
The app already runs external commands for events:
{
"incomingCallCommand": "/path/to/script.sh",
"incomingCallCommandArgs": ["caller", "text", "image"]
}
We could extend this pattern with:
{
"screenLockCommand": "/path/to/user-script.sh",
"screenLockCommandArgs": ["lock|unlock"]
}
But this is backwards - we want the user's lock script to call us, not us calling the user's script.
Recommended Linux Solution
Provide new MQTT commands that users can invoke from their own screen lock handlers.
User's Workflow
Step 1: User sets up screen lock listener (their responsibility)
Example for GNOME:
#!/bin/bash
# ~/.local/bin/teams-lock-privacy.sh
dbus-monitor --session "type='signal',interface='org.gnome.ScreenSaver'" |
while read -r line; do
if echo "$line" | grep -q "boolean true"; then
# Screen locked
mosquitto_pub -h localhost -t "teams/command" \
-m '{"action":"disable-media"}' -q 1
elif echo "$line" | grep -q "boolean false"; then
# Screen unlocked (optional)
# User can choose whether to re-enable or leave disabled
# mosquitto_pub -h localhost -t "teams/command" \
# -m '{"action":"enable-media"}' -q 1
fi
done
Step 2: Teams for Linux handles the command (our responsibility)
New MQTT command handlers:
disable-media- Stop all active camera and microphone tracksenable-media- Allow media requests again (doesn't auto-start)query-media-state- Return current media state
Benefits of This Approach
✅ Linux philosophy - Composable tools, user control
✅ Desktop agnostic - Works with GNOME, KDE, XFCE, sway, etc.
✅ Reuses existing infrastructure - MQTT already implemented
✅ Flexible - Users can customize behavior, add delays, logging, etc.
✅ Optional - Users opt-in by writing their own integration
✅ Testable - Users can manually test with mosquitto_pub
Challenges
⚠️ Requires MQTT enabled - Not all users have MQTT configured ⚠️ User setup required - Not "automatic" like native Windows/macOS ⚠️ Documentation needed - Must provide clear examples for different DEs
Alternative: D-Bus Interface
Instead of (or in addition to) MQTT, Teams for Linux could expose its own D-Bus interface:
# User's lock script calls our D-Bus method
dbus-send --session --dest=org.teamsforlinux.MediaPrivacy \
--type=method_call /org/teamsforlinux/MediaPrivacy \
org.teamsforlinux.MediaPrivacy.DisableMedia
Benefits:
- More "native" to Linux desktop integration
- Doesn't require MQTT broker
- Can be called synchronously
Drawbacks:
- Additional implementation complexity
- Another integration point to maintain
- MQTT is already implemented and working
Implementation Approach
Recommended: Three-Tier Implementation
Support all platforms with appropriate approaches:
Tier 1: MQTT Commands (Linux Focus - Immediate)
Add new MQTT commands for user-script integration:
-
New MQTT Actions (extend
app/mqtt/index.js):this.actionShortcutMap = {
'toggle-mute': 'Ctrl+Shift+M',
'toggle-video': 'Ctrl+Shift+O',
'toggle-hand-raise': 'Ctrl+Shift+K',
// NEW COMMANDS:
'disable-media': null, // Custom handler, not keyboard shortcut
'enable-media': null, // Custom handler, not keyboard shortcut
}; -
New browser tool:
app/browser/tools/mediaPrivacy.js- Track active MediaStream objects
- Handle
disable-mediacommand: Stop all camera/mic tracks - Handle
enable-mediacommand: Remove block on getUserMedia - Track state for optional restore
- Expose via IPC for MQTT to call
-
Documentation (primary deliverable):
- Add section to
docs-site/docs/mqtt-integration.md - Provide example scripts for:
- GNOME (
org.gnome.ScreenSaver) - KDE (
org.freedesktop.ScreenSaver) - Cinnamon (
org.cinnamon.ScreenSaver) - systemd user session hooks
- Generic D-Bus monitor approach
- GNOME (
- Add section to
Benefits:
- ✅ Works TODAY with existing infrastructure
- ✅ Aligns with Linux philosophy
- ✅ Maximum flexibility for users
- ✅ No desktop environment dependencies
- ✅ Users can test immediately with
mosquitto_pub
User experience:
# User's GNOME lock script
mosquitto_pub -h localhost -t "teams/command" \
-m '{"action":"disable-media"}' -q 1
Tier 2: Native Events (macOS/Windows - Optional)
Add automatic handling for platforms with native support:
-
New main process module:
app/screenLockPrivacy/index.js- Listen to
powerMonitorlock-screen/unlock-screenevents - Automatically call media privacy functions
- Configuration:
screenLockPrivacy.enabled: false(opt-in)
- Listen to
-
Reuse browser tool from Tier 1
- Same media control logic
- Called automatically instead of via MQTT
Benefits:
- Automatic for macOS/Windows users
- Leverages native OS events
- Still reuses core media control logic
Configuration:
{
"screenLockPrivacy": {
"enabled": true, // macOS/Windows only
"disableCamera": true,
"disableMicrophone": true,
"restoreOnUnlock": false
}
}
Tier 3: Polling Fallback (Linux - Optional)
For users who don't want to set up scripts:
-
Extend
IdleMonitor(app/idle/monitor.js)- Detect state transitions to/from "locked"
- Trigger media privacy functions automatically
- Configuration:
screenLockPrivacy.usePolling: true
-
Reuse browser tool from Tier 1
- Same media control logic
- Called automatically via polling
Benefits:
- Works without user scripts
- Cross-desktop-environment on Linux
- Opt-in for users who want "automatic" behavior
Drawbacks:
- Polling overhead (5-10 second intervals)
- Less precise than event-based detection
- May not work on all distributions
Prioritization
Recommended order:
- FIRST: Tier 1 (MQTT Commands) - Biggest impact for Linux users
- SECOND: Documentation - Critical for adoption
- THIRD: Tier 2 (Native Events) - Nice-to-have for macOS/Windows
- MAYBE: Tier 3 (Polling) - Only if users request it
Testing Considerations
Manual Testing:
- Test with
mosquitto_pubto verify media disabling works - Lock screen during active call - verify camera/mic disabled via user script
- Test across desktop environments (GNOME, KDE, XFCE, i3)
- Verify systemd service starts correctly and survives session restarts
Edge Cases:
- Screen lock during ongoing screen share
- Rapid lock/unlock cycles
- User's script calling disable-media when already disabled
- MQTT broker temporarily unavailable
Security and Privacy Considerations
Benefits
- ✅ Prevents unintended audio/video transmission
- ✅ Aligns with privacy-by-design principles
- ✅ Matches Windows Teams client behavior
- ✅ User control via configuration
Risks
- ⚠️ Could interrupt important calls if screen locks unexpectedly
- ⚠️ User may not realize media was disabled
- ⚠️ Teams UI might show unexpected state changes
Mitigations
- Feature is opt-in (requires user to set up script + MQTT)
- Users can add notification to their own scripts
- Users control exact behavior via their lock script
- Document behavior and provide working examples
- Users decide whether to re-enable on unlock (commented in examples)
Related Work
Similar Implementations
- KeePassXC: Uses D-Bus for Linux lock detection (Electron Issue #38088)
- Windows Teams Client: Native implementation (proprietary)
- Zoom: Has similar privacy features on some platforms
Dependencies
Potential npm packages for Linux:
dbus-next- Modern D-Bus library for Node.jsdbus-native- Alternative D-Bus implementation- Neither adds burden if used conditionally on Linux only
Recommendations
Immediate Actions
- ✅ Create GitHub issue comment with investigation findings
- ✅ Discuss with maintainer about Linux-first MQTT approach
- Create Architecture Decision Record (ADR) if approved for implementation
Open Questions for Maintainer
- Is the user-script approach acceptable, or prefer automatic detection?
- Should we also expose a D-Bus interface in addition to MQTT?
- Should screen sharing also be disabled, or only camera/microphone?
- Should
disable-mediablock new media requests, or just stop existing tracks? - Priority: Implement Tier 1 now, or backlog for future release?
References
Electron Documentation
Issues and Discussions
- Electron Issue #38088 - Linux lock-screen support
- Teams for Linux Issue #2015 - Disable camera/mic on lock
D-Bus Resources
- Ask Ubuntu - Monitor screen lock/unlock with D-Bus
- Linux Mint Forums - D-Bus signals for monitor power
Appendix A: User Integration Scripts
These are example scripts users can deploy to integrate screen lock with Teams for Linux media privacy.
GNOME (Ubuntu, Fedora, etc.)
#!/bin/bash
# ~/.local/bin/teams-lock-privacy.sh
# Monitors GNOME screen lock and disables Teams media
MQTT_HOST="localhost"
MQTT_TOPIC="teams/command"
dbus-monitor --session "type='signal',interface='org.gnome.ScreenSaver'" |
while read -r line; do
if echo "$line" | grep -q "boolean true"; then
echo "$(date): Screen locked - disabling Teams media"
mosquitto_pub -h "$MQTT_HOST" -t "$MQTT_TOPIC" \
-m '{"action":"disable-media","timestamp":"'"$(date -Iseconds)"'"}' -q 1
elif echo "$line" | grep -q "boolean false"; then
echo "$(date): Screen unlocked"
# Optionally enable media on unlock:
# mosquitto_pub -h "$MQTT_HOST" -t "$MQTT_TOPIC" \
# -m '{"action":"enable-media"}' -q 1
fi
done
Autostart with systemd:
# ~/.config/systemd/user/teams-lock-privacy.service
[Unit]
Description=Teams for Linux screen lock media privacy
After=graphical-session.target
[Service]
Type=simple
ExecStart=%h/.local/bin/teams-lock-privacy.sh
Restart=on-failure
RestartSec=5
[Install]
WantedBy=default.target
Enable:
chmod +x ~/.local/bin/teams-lock-privacy.sh
systemctl --user enable --now teams-lock-privacy.service
KDE Plasma
#!/bin/bash
# ~/.local/bin/teams-lock-privacy-kde.sh
# Monitors KDE screen lock and disables Teams media
MQTT_HOST="localhost"
MQTT_TOPIC="teams/command"
dbus-monitor --session "type='signal',interface='org.freedesktop.ScreenSaver'" |
while read -r line; do
if echo "$line" | grep -q "boolean true"; then
echo "$(date): Screen locked - disabling Teams media"
mosquitto_pub -h "$MQTT_HOST" -t "$MQTT_TOPIC" \
-m '{"action":"disable-media"}' -q 1
elif echo "$line" | grep -q "boolean false"; then
echo "$(date): Screen unlocked"
fi
done
Cinnamon (Linux Mint)
#!/bin/bash
# ~/.local/bin/teams-lock-privacy-cinnamon.sh
MQTT_HOST="localhost"
MQTT_TOPIC="teams/command"
dbus-monitor --session "type='signal',interface='org.cinnamon.ScreenSaver'" |
while read -r line; do
if echo "$line" | grep -q "boolean true"; then
mosquitto_pub -h "$MQTT_HOST" -t "$MQTT_TOPIC" \
-m '{"action":"disable-media"}' -q 1
fi
done
Generic D-Bus Monitor (Any Desktop)
This version listens for multiple screen saver interfaces:
#!/bin/bash
# ~/.local/bin/teams-lock-privacy-generic.sh
MQTT_HOST="localhost"
MQTT_TOPIC="teams/command"
# Listen for any screen saver lock signal
dbus-monitor --session "type='signal',member='ActiveChanged'" |
while read -r line; do
if echo "$line" | grep -q "boolean true"; then
echo "$(date): Screen lock detected - disabling Teams media"
mosquitto_pub -h "$MQTT_HOST" -t "$MQTT_TOPIC" \
-m '{"action":"disable-media"}' -q 1
elif echo "$line" | grep -q "boolean false"; then
echo "$(date): Screen unlock detected"
fi
done
i3/sway (Manual Lock with i3lock/swaylock)
Wrap your lock command:
#!/bin/bash
# ~/.local/bin/lock-with-teams-privacy.sh
# Disable Teams media BEFORE locking
mosquitto_pub -h localhost -t "teams/command" \
-m '{"action":"disable-media"}' -q 1
# Lock the screen
i3lock -c 000000 # or: swaylock
# Optionally enable on unlock:
# mosquitto_pub -h localhost -t "teams/command" \
# -m '{"action":"enable-media"}' -q 1
Bind in i3 config:
bindsym $mod+Shift+x exec ~/.local/bin/lock-with-teams-privacy.sh
Testing Your Integration
Test manually without locking:
# Disable media
mosquitto_pub -h localhost -t "teams/command" \
-m '{"action":"disable-media"}' -q 1
# Verify camera/mic are stopped in Teams UI
# Enable media
mosquitto_pub -h localhost -t "teams/command" \
-m '{"action":"enable-media"}' -q 1
Appendix B: Code Examples
Example: MQTT Command Handler (Tier 1)
// app/screenLockPrivacy/index.js
const { ipcMain, powerMonitor } = require('electron');
class ScreenLockPrivacy {
#config;
#window;
#isLocked = false;
constructor(config, mainWindow) {
this.#config = config;
this.#window = mainWindow;
}
initialize() {
if (!this.#config.screenLockPrivacy?.enabled) {
console.debug('[SCREEN_LOCK_PRIVACY] Feature disabled in configuration');
return;
}
// Try native events first (macOS/Windows)
if (process.platform === 'darwin' || process.platform === 'win32') {
this.#setupNativeEvents();
} else {
// Linux: Use polling
this.#setupPolling();
}
console.info('[SCREEN_LOCK_PRIVACY] Initialized');
}
#setupNativeEvents() {
powerMonitor.on('lock-screen', () => {
this.#handleScreenLock();
});
powerMonitor.on('unlock-screen', () => {
this.#handleScreenUnlock();
});
console.debug('[SCREEN_LOCK_PRIVACY] Using native lock-screen events');
}
#setupPolling() {
// Poll every 5 seconds
setInterval(() => {
const state = powerMonitor.getSystemIdleState(60);
const isNowLocked = state === 'locked';
if (isNowLocked !== this.#isLocked) {
if (isNowLocked) {
this.#handleScreenLock();
} else {
this.#handleScreenUnlock();
}
}
}, 5000);
console.debug('[SCREEN_LOCK_PRIVACY] Using polling for lock detection');
}
#handleScreenLock() {
this.#isLocked = true;
console.info('[SCREEN_LOCK_PRIVACY] Screen locked - disabling media devices');
this.#window.webContents.send('screen-lock-privacy:lock');
}
#handleScreenUnlock() {
this.#isLocked = false;
console.info('[SCREEN_LOCK_PRIVACY] Screen unlocked');
this.#window.webContents.send('screen-lock-privacy:unlock');
}
}
module.exports = ScreenLockPrivacy;
Example: Browser Tool
// app/browser/tools/mediaPrivacy.js
const activeStreams = new WeakSet();
let trackedTracks = [];
function init(config, ipcRenderer) {
if (!config.screenLockPrivacy?.enabled) {
console.debug('[MEDIA_PRIVACY] Feature disabled in configuration');
return;
}
// Track getUserMedia calls
const originalGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);
navigator.mediaDevices.getUserMedia = async function(constraints) {
const stream = await originalGetUserMedia(constraints);
trackMediaStream(stream);
return stream;
};
// Listen for lock events
ipcRenderer.on('screen-lock-privacy:lock', () => {
disableAllMediaTracks();
});
ipcRenderer.on('screen-lock-privacy:unlock', () => {
// Optionally restore based on config
if (config.screenLockPrivacy.restoreOnUnlock) {
console.debug('[MEDIA_PRIVACY] Auto-restore not implemented');
}
});
console.info('[MEDIA_PRIVACY] Media privacy protection initialized');
}
function trackMediaStream(stream) {
if (!stream || activeStreams.has(stream)) return;
activeStreams.add(stream);
stream.getTracks().forEach(track => {
trackedTracks.push({
track,
kind: track.kind,
wasActive: true
});
});
}
function disableAllMediaTracks() {
console.info('[MEDIA_PRIVACY] Disabling all active media tracks');
trackedTracks.forEach(({ track, kind }) => {
if (track.readyState === 'live') {
track.stop();
console.debug(`[MEDIA_PRIVACY] Stopped ${kind} track`);
}
});
// Clear inactive tracks
trackedTracks = trackedTracks.filter(({ track }) => track.readyState === 'live');
}
module.exports = { init };
Conclusion
This feature is technically feasible and recommended for implementation with a Linux-first, user-empowering approach.
Key Recommendations
-
Start with MQTT command extension (Tier 1)
- Immediate value for Linux users
- Leverages existing infrastructure
- Aligns with Unix philosophy
- Minimal implementation effort
-
Provide comprehensive documentation
- Example scripts for major desktop environments
- systemd integration patterns
- Testing procedures
- This is MORE important than the code
-
Optional: Add native event handling for macOS/Windows users who want automatic behavior
Why This Approach Works
✅ Embraces Linux philosophy - Composable tools, user control ✅ Desktop environment agnostic - Works everywhere ✅ Leverages existing code - MQTT already implemented and tested ✅ User empowerment - Users can customize, log, add delays, etc. ✅ Testable - Users can verify behavior before integrating ✅ Optional - Users opt-in by choice
Implementation Effort
Tier 1 (MQTT Commands):
- Code: ~200 lines (browser tool + MQTT handler extension)
- Documentation: Critical - provide ready-to-use scripts
Tier 2 (Native Events):
- Code: ~150 lines (main process module)
- Documentation: Configuration examples
Tier 3 (Polling):
- Code: ~100 lines (IdleMonitor extension)
Alignment with Project Goals
The feature aligns with:
- ✅ User privacy expectations
- ✅ Linux-first development philosophy
- ✅ Unix composability principles
- ✅ Existing MQTT integration patterns
- ✅ Power user customization abilities
This approach turns a "Linux limitation" into a "Linux strength" by empowering users to integrate Teams with their desktop environment in their own way.