Feat: screenshare audio sharing in Linux (with pipewire)#2880
Feat: screenshare audio sharing in Linux (with pipewire)#2880JoaoCostaIFG wants to merge 13 commits intoelement-hq:developfrom
Conversation
On Windows, include audio: loopback in the displayMedia callback when the web page requests audio. This enables sharing system audio alongside screen/window video during screen shares. The implementation: - Tracks whether audio was requested via the request parameter in setDisplayMediaRequestHandler - Stores the audioRequested state in displayMediaCallback module - On Windows, when the user selects a source via the custom picker, includes loopback audio in the callback to capture system audio - Captures audio state before async callback to prevent race conditions
…ntegration Add @vencord/venmic as an optional dependency to enable audio sharing during screen shares on Linux via PipeWire virtual microphone. New files: - src/venmic.ts: Main process module that interfaces with venmic's PatchBay to list audio nodes, create/destroy virtual microphones - src/@types/vencord__venmic.d.ts: Type definitions for the venmic module Changes: - src/electron-main.ts: Conditionally import venmic on Linux - src/preload.cts: Expose venmic IPC APIs (list, start, startSystem, stop) - electron-builder.ts: Include venmic .node files in packaged app (Linux only) - package.json: Add @vencord/venmic optional dependency - knip.ts: Acknowledge venmic as a used dependency Also includes: - Stricter GLIBC version check and logging for missing Audio Service - Load native .node file directly using createRequire for packaged apps - Fix unlink() and stop() return types to match actual API
Allow users to choose between sharing entire system audio, per-application audio, or no audio for Linux screen shares. This gives explicit control and avoids always sharing all system sounds, addressing privacy and user intent. It also ensures compatibility with Element Call which may require audio devices in iframes, requiring broad injection of the virtual microphone in web contexts. Implementation details: - Audio picker dialog for selecting audio source - Support for per-app audio via stable properties (application.name or node.name) for reliable matching across app restarts - Properly stop venmic when "No audio" is selected - Virtual microphone injection in web contexts for Element Call
- Add keyboard navigation (arrow keys, Home, End) with proper focus management - Add ARIA attributes (role=radiogroup/radio, aria-checked, aria-live) - Add focus-visible styles for keyboard users - Align colors with Element design system (green accent #0dbd8b) - Use CSS variables for theming and consistent spacing - Improve color contrast for WCAG AA compliance - Add prefers-reduced-motion support - Add loading spinner visual indicator - Style scrollbars to match dark theme - Add double-click protection on submit - Wrap JSON.parse in try-catch for robustness - Disable Share button during loading - Remove console.log debug statements - Add semantic HTML (<main> landmark)
- Copy venmic addon to lib/ during build instead of bundling node_modules - Simplify native module path to match Vesktop's approach - Rename isGlibCxxOutdated to isGlibcOutdated (matches actual detection) - Simplify GLIBC detection using string matching - Parallelize iframe injection for better performance - Fix ReadableStream type error in webcontents-handler - Update copyright headers to 2026
- Show audio picker for Linux X11 path (Wayland already handled in electron-main.ts) - Fix potential crash when audioTrack is undefined before addTrack() - Extract shared AudioSelection and VenmicListResult types to @types/audio-sharing.d.ts - Reduce logging verbosity by using console.debug for operational messages
scripts/copy-res.ts
Outdated
|
|
||
| // Copy venmic native addon for Linux audio sharing | ||
| // Only copy on Linux since venmic is Linux-only | ||
| if (process.platform === "linux") { |
There was a problem hiding this comment.
This won't work for cross-compiling. You can cross compile for linux from mac or Windows.
There was a problem hiding this comment.
I moved it to the hak module build system. Both the development and the packaged build worked on my side. It has handled not having venmic installed.
Hope the change made sense.
I don't have windows or mac machines to test the cross compilation :/
Move venmic native addon handling from copy-res.ts (which checks process.platform) to the HAK build system (which checks target platform). This allows building Linux packages from Windows or macOS hosts. Signed-off-by: JoaoCostaIFG <me@joaocosta.dev>
3af732b to
d840ba8
Compare
- Fix import ordering in venmic.ts and webcontents-handler.ts - Replace inline import() type annotation with proper type import - Fix prettier formatting in hak venmic build/check files
build/audio-picker.html
Outdated
| @@ -0,0 +1,541 @@ | |||
| <!DOCTYPE html> | |||
| <!-- | |||
| Copyright 2025 New Vector Ltd. | |||
There was a problem hiding this comment.
Wrong year & Company name, this should be your copyright
There was a problem hiding this comment.
I thought that was the company to be placed in all copyright headers. Didn't know I was supposed to use my copyright.
build/audio-picker.html
Outdated
| --color-bg-canvas: #15191e; | ||
| --color-bg-subtle: #21262d; | ||
| --color-bg-subtle-hover: #282e36; | ||
| --color-bg-accent: rgba(13, 189, 139, 0.15); | ||
| --color-border-accent: #0dbd8b; | ||
| --color-accent: #0dbd8b; | ||
| --color-accent-hover: #0aa87c; | ||
| --color-text-primary: #ffffff; | ||
| --color-text-secondary: #a9b2bc; | ||
| --color-text-muted: #8d99a5; | ||
| --color-border-subtle: #30363d; | ||
| --color-border-interactive: #6e7681; | ||
| --color-bg-button-secondary: #30363d; | ||
| --color-bg-button-secondary-hover: #3d444d; | ||
| --color-bg-error: rgba(255, 75, 85, 0.15); | ||
| --color-border-error: rgba(255, 75, 85, 0.4); | ||
| --color-text-error: #ff4b55; | ||
| --color-focus-ring: rgba(13, 189, 139, 0.4); |
There was a problem hiding this comment.
This ought to use actual Compound Design Tokens rather than magic hardcoded values
There was a problem hiding this comment.
I'm not sure if this is the best approach, but I added @vector-im/compound-design-tokens as a devDependency to copy the css to the build directory (build/compound). This makes it available to the audio picker window.
It also picks the current theme from the parent window (element itself): light/dark or match system. I haven't tried custom themes.
Changing the theme with the audio picker open does not change its appearance: only on the next run of the picker.
build/audio-picker.html
Outdated
| } | ||
|
|
||
| body { | ||
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; |
There was a problem hiding this comment.
We should be consistent on font, should be using Inter
There was a problem hiding this comment.
I'm now using the font family defined by Compound Design Tokens. I think that this window doesn't have access to fonts bundled with element-desktop tho. I.e. it will only use Inter if it installed in the OS. Not sure if there's much we can do about that with this approach.
build/audio-picker.html
Outdated
| <h1>Share Audio</h1> | ||
| <p class="subtitle">Choose what audio to share with the call</p> | ||
|
|
||
| <div id="content" class="loading" aria-live="polite"> | ||
| <div class="spinner" aria-hidden="true"></div> | ||
| <span>Loading audio sources...</span> | ||
| </div> | ||
| </main> | ||
|
|
||
| <div class="buttons"> | ||
| <button type="button" class="btn-secondary" id="cancel">Cancel</button> | ||
| <button type="button" class="btn-primary" id="share" disabled>Share</button> | ||
| </div> |
hak/@vencord/venmic/build.ts
Outdated
| @@ -0,0 +1,54 @@ | |||
| /* | |||
| Copyright 2026 New Vector Ltd. | |||
There was a problem hiding this comment.
Ditto previous copyright comment
hak/@vencord/venmic/check.ts
Outdated
| @@ -0,0 +1,23 @@ | |||
| /* | |||
| Copyright 2026 New Vector Ltd. | |||
src/@types/audio-sharing.d.ts
Outdated
| @@ -0,0 +1,21 @@ | |||
| /* | |||
| Copyright 2025 New Vector Ltd. | |||
src/@types/vencord__venmic.d.ts
Outdated
| @@ -0,0 +1,36 @@ | |||
| /* | |||
| Copyright 2025 New Vector Ltd. | |||
src/audio-picker-preload.cts
Outdated
| @@ -0,0 +1,36 @@ | |||
| /* | |||
| Copyright 2025 New Vector Ltd. | |||
src/audio-picker.ts
Outdated
| @@ -0,0 +1,139 @@ | |||
| /* | |||
| Copyright 2025 New Vector Ltd. | |||
src/audio-picker.ts
Outdated
| minimizable: false, | ||
| maximizable: false, | ||
| fullscreenable: false, | ||
| title: "Share Audio", |
src/electron-main.ts
Outdated
| if (setupVenmicInjection) { | ||
| setupVenmicInjection(global.mainWindow.webContents); |
There was a problem hiding this comment.
| if (setupVenmicInjection) { | |
| setupVenmicInjection(global.mainWindow.webContents); | |
| setupVenmicInjection?.(global.mainWindow.webContents); |
src/ipc.ts
Outdated
|
|
||
| // Include loopback audio for Windows | ||
| if (audioRequested && process.platform === "win32") { | ||
| await callback?.({ video: args[0], audio: "loopback" }); |
There was a problem hiding this comment.
The types say this isn't a promise?
There was a problem hiding this comment.
You're right. I removed the win32 special case because it isn't really part of the scope of this PR. Also, it isn't a nice solution for audio on windows because it causes call participants to start hearing themselves
src/ipc.ts
Outdated
| if (audioRequested && process.platform === "win32") { | ||
| await callback?.({ video: args[0], audio: "loopback" }); | ||
| } else { | ||
| await callback?.({ video: args[0] }); |
src/venmic-inject.ts
Outdated
| @@ -0,0 +1,140 @@ | |||
| /* | |||
| Copyright 2026 New Vector Ltd. | |||
src/venmic.ts
Outdated
| @@ -0,0 +1,143 @@ | |||
| /* | |||
| Copyright 2025 New Vector Ltd. | |||
Signed-off-by: JoaoCostaIFG <me@joaocosta.dev>
- Add translated UI strings to audio picker (title, buttons, labels, error messages) - Expose getStrings IPC handler for loading localized strings in the picker UI - Update HTML template to dynamically populate translated content - Add audio_picker translation keys to en_EN.json - Revert win32 'audio: loopback' automatic addition as it's outside scope - Improve error handling for stopVenmic IPC calls in preload Signed-off-by: JoaoCostaIFG <me@joaocosta.dev>
- Replace hardcoded CSS design token values in audio-picker.html with Compound Design Token from @vector-im/compound-design-tokens: - Add @vector-im/compound-design-tokens dev dependency for CSS tokens - Copy Compound CSS to build/compound/ during build (scripts/copy-res.ts) - Include compound CSS in packaged app resources (electron-builder.ts) - Add theme detection from parent window to support light/dark/high-contrast themes - Add IPC handler (audio-picker-get-config) to pass theme preference to picker window - Add fallback to system preference when parent window theme cannot be detected Signed-off-by: JoaoCostaIFG <me@joaocosta.dev>
|
Sorry but this repo has been merged into https://github.com/element-hq/element-web/tree/develop/apps/desktop so your PR will need to be moved there |
Checklist
public/exportedsymbols have accurate TSDoc documentation.Description
Will help implement the feature requested in this issue: element-hq/element-call #3657.
Problems
Next steps (outside the scope of this PR)
Screenshots
Audio sharing screen: