Skip to content
This repository was archived by the owner on Mar 25, 2026. It is now read-only.

Feat: screenshare audio sharing in Linux (with pipewire)#2880

Closed
JoaoCostaIFG wants to merge 13 commits intoelement-hq:developfrom
JoaoCostaIFG:feat/screenshare-app-audio-sharing
Closed

Feat: screenshare audio sharing in Linux (with pipewire)#2880
JoaoCostaIFG wants to merge 13 commits intoelement-hq:developfrom
JoaoCostaIFG:feat/screenshare-app-audio-sharing

Conversation

@JoaoCostaIFG
Copy link
Copy Markdown

@JoaoCostaIFG JoaoCostaIFG commented Mar 14, 2026

Checklist

  • Ensure your code works with manual testing.
  • New or updated public/exported symbols have accurate TSDoc documentation.
  • Linter and other CI checks pass.
  • I have licensed the changes to Element by completing the Contributor License Agreement (CLA)

Description

  • This adds support for streaming application audio during screenshares on Linux systems using PipeWire:
    • Mostly Wayland, but works the same on X11 as long as PipeWire is available/in use.
  • Uses venmic which is a package used by vesktop
    • Vesktop is custom 3rd party discord client
    • It allows screenshares with audio in Linux
  • Feature kept as optional:
    • Only works when in Linux, with Pipewire.
    • If venmic fails to load (outdated glibc, missing PipeWire, etc.), the screen share works normally without audio

Will help implement the feature requested in this issue: element-hq/element-call #3657.

Problems

  • I've never loaded a prebuilt node module like this, so I'm not too sure I'm doing it right.
  • I was expecting an easier/better way to load the audio source/channel/node picker dialog window
    • Open to suggestions here.
  • Venmic isn't exactly intended to be used in other apps like this, but it seems fine to me.

Next steps (outside the scope of this PR)

  • Screenshares currenly lack audio volume controls:
    • Need to check that the audio is being sent as a separate stream and add a volume slider for it.
  • Get something similar working on Windows and on MacOS.

Screenshots

Audio sharing screen:

image

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
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Mar 14, 2026

CLA assistant check
All committers have signed the CLA.

@github-actions github-actions bot added the Z-Community-PR Issue is solved by a community member's PR label Mar 14, 2026
@JoaoCostaIFG JoaoCostaIFG changed the title Feat/screenshare app audio sharing Feat: screenshare audio sharing in Linux (with pipewire) Mar 14, 2026

// Copy venmic native addon for Linux audio sharing
// Only copy on Linux since venmic is Linux-only
if (process.platform === "linux") {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This won't work for cross-compiling. You can cross compile for linux from mac or Windows.

Copy link
Copy Markdown
Author

@JoaoCostaIFG JoaoCostaIFG Mar 14, 2026

Choose a reason for hiding this comment

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

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>
@JoaoCostaIFG JoaoCostaIFG force-pushed the feat/screenshare-app-audio-sharing branch from 3af732b to d840ba8 Compare March 14, 2026 21:35
- 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
@@ -0,0 +1,541 @@
<!DOCTYPE html>
<!--
Copyright 2025 New Vector Ltd.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Wrong year & Company name, this should be your copyright

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I thought that was the company to be placed in all copyright headers. Didn't know I was supposed to use my copyright.

Comment on lines +17 to +34
--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);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This ought to use actual Compound Design Tokens rather than magic hardcoded values

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

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.

}

body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should be consistent on font, should be using Inter

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

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.

Comment on lines +300 to +312
<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>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Needs i18n

@@ -0,0 +1,54 @@
/*
Copyright 2026 New Vector Ltd.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ditto previous copyright comment

@@ -0,0 +1,23 @@
/*
Copyright 2026 New Vector Ltd.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ditto

@@ -0,0 +1,21 @@
/*
Copyright 2025 New Vector Ltd.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ditto

@@ -0,0 +1,36 @@
/*
Copyright 2025 New Vector Ltd.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ditto

@@ -0,0 +1,36 @@
/*
Copyright 2025 New Vector Ltd.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ditto

@@ -0,0 +1,139 @@
/*
Copyright 2025 New Vector Ltd.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ditto

minimizable: false,
maximizable: false,
fullscreenable: false,
title: "Share Audio",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

i18n

Comment on lines +587 to +588
if (setupVenmicInjection) {
setupVenmicInjection(global.mainWindow.webContents);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
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" });
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The types say this isn't a promise?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

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] });
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ditto

@@ -0,0 +1,140 @@
/*
Copyright 2026 New Vector Ltd.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copyright ditto

src/venmic.ts Outdated
@@ -0,0 +1,143 @@
/*
Copyright 2025 New Vector Ltd.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ditto

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>
@t3chguy
Copy link
Copy Markdown
Member

t3chguy commented Mar 25, 2026

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

@t3chguy t3chguy closed this Mar 25, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

T-Enhancement Z-Community-PR Issue is solved by a community member's PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants