Skip to content

Commit

Permalink
Pure bash migration - Update 0.2.0 (#10)
Browse files Browse the repository at this point in the history
* Move to pw-dump

* Switch to bash for single node stream

* Remove code reference

* Switch from pid to pipewire id

* Switch from grep to jq

* All desktop audio

* Virtmic bash fixes

* Always use bash virtmic implementation

* Remove notify-send

* Automatically connect new nodes in All Desktop mode

* Fix GUI stuck on sharing

* Fix indentations

* Kill nodes watch on virtmic stop

* Remove watcher.sh script

* Remove all C++ code

* Remove git submodules

* Update README.md

* Fix race condition

* Fix watcher connecting excluded nodes

* Add check for targetNodesIds

* Kill the background processes with SIGINT

* Fix background watch crashes

* Fix exclusion in nodes watch

* Add version checks

* Update readme with new install instructions

* Bump extension version

* Add manual install script

---------

Co-authored-by: IceDBorn <[email protected]>
  • Loading branch information
jim3692 and IceDBorn authored Jul 19, 2023
1 parent a22e9a9 commit 2d9282d
Show file tree
Hide file tree
Showing 14 changed files with 218 additions and 293 deletions.
3 changes: 0 additions & 3 deletions .gitmodules

This file was deleted.

19 changes: 5 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,19 @@ Based on [link-app-to-mic](https://github.com/Soundux/rohrkabel/tree/master/exam
[![AUR](https://img.shields.io/aur/version/pipewire-screenaudio?style=for-the-badge)](https://aur.archlinux.org/packages/pipewire-screenaudio)
[![AUR](https://img.shields.io/aur/version/pipewire-screenaudio-git?style=for-the-badge)](https://aur.archlinux.org/packages/pipewire-screenaudio-git)

### Building from Source
### Installing from Source
#### Requirements
- cmake
- gawk
- hexdump
- jq
- pipewire
- pipewire-pulse
- tl-expected
- psmisc

### Building
```bash
git clone --recursive https://github.com/IceDBorn/pipewire-screenaudio.git
cd pipewire-screenaudio/native
bash build.sh
git clone https://github.com/IceDBorn/pipewire-screenaudio.git
cd pipewire-screenaudio
bash install.sh
```

### Installing
- Edit `pipewire-screenaudio/native/native-messaging-hosts/firefox.json`, replace "path" with the full location of `pipewire-screenaudio/native/connector/pipewire-screen-audio-connector.sh`
- Rename `firefox.json` to `com.icedborn.pipewirescreenaudioconnector.json` and move it to `~/.mozilla/native-messaging-hosts`
- Install the [extension](https://addons.mozilla.org/en-US/firefox/addon/pipewire-screenaudio/) for Firefox

## Usage
- Optional: Grant extension with access permissions to all sites
- Join a WebRTC call, click the extension icon, select an audio node and share
Expand Down
2 changes: 1 addition & 1 deletion extension/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

"manifest_version": 3,
"name": "Pipewire Screenaudio",
"version": "0.1.1",
"version": "0.2.0",

"description": "Passthrough pipewire audio to WebRTC screenshare",

Expand Down
10 changes: 5 additions & 5 deletions extension/scripts/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ function handleMessage (response) {
if (response.message === 'node-shared') {
// Passthrough the selected node to pipewire-screenaudio
chrome.runtime.sendNativeMessage(response.messageName, { cmd: response.cmd, args: response.args })
.then(({ micPid }) => {
window.localStorage.setItem('micPid', micPid)
chrome.runtime.sendMessage('pid-updated')
.then(({ micId }) => {
window.localStorage.setItem('micId', micId)
chrome.runtime.sendMessage('mic-id-updated')
})
}

if (response.message === 'node-stopped') {
chrome.runtime.sendNativeMessage(response.messageName, { cmd: response.cmd, args: response.args })
.then(() => {
window.localStorage.setItem('micPid', null)
chrome.runtime.sendMessage('pid-removed')
window.localStorage.setItem('micId', null)
chrome.runtime.sendMessage('mic-id-removed')
})
}
}
Expand Down
66 changes: 41 additions & 25 deletions extension/scripts/popup.js
Original file line number Diff line number Diff line change
@@ -1,51 +1,57 @@
const MESSAGE_NAME = 'com.icedborn.pipewirescreenaudioconnector'
// const ALL_DESKTOP_AUDIO_TEXT = 'All Desktop Audio'
const EXT_VERSION = browser.runtime.getManifest().version

const dropdown = document.getElementById('dropdown')
const message = document.getElementById('message')
const buttonGroup = document.getElementById('btn-group')

let selectedNode = null
let blacklistBtnEl = null
let nodesLoop = null

async function isRunning () {
const micPid = window.localStorage.getItem('micPid')
if (!micPid) {
const micId = window.localStorage.getItem('micId')
if (!micId) {
return false
}

const { isRunning } = await chrome.runtime.sendNativeMessage(MESSAGE_NAME, { cmd: 'IsPipewireScreenAudioRunning', args: [{ micPid }] })
const { isRunning } = await chrome.runtime.sendNativeMessage(MESSAGE_NAME, { cmd: 'IsPipewireScreenAudioRunning', args: [{ micId }] })

if (!isRunning) {
window.localStorage.setItem('micPid', null)
window.localStorage.setItem('micId', null)
}

return isRunning
}

function createShareBtn (root) {
if (document.getElementById('share-btn')) return
const shareBtn = document.createElement('button')
shareBtn.id = 'share-btn'
shareBtn.className = 'btn btn-success me-2'
shareBtn.innerText = 'Share'
root.appendChild(shareBtn)

const shareBtnEl = document.getElementById('share-btn')
shareBtnEl.addEventListener('click', () => {

const eventListener = () => {
shareBtnEl.removeEventListener('click', eventListener)
const spinner = document.createElement('span')
const text = document.createElement('span')
shareBtnEl.innerText = ''
spinner.className = 'spinner-border spinner-border-sm me-1'
text.innerText = 'Sharing...'
clearInterval(nodesLoop)
shareBtnEl.appendChild(spinner)
shareBtnEl.appendChild(text)
root.removeChild(blacklistBtnEl)
document.getElementById('blacklist-btn').hidden = true

chrome.runtime.sendMessage({ messageName: MESSAGE_NAME, message: 'node-shared', cmd: 'StartPipewireScreenAudio', args: [{ node: selectedNode }] })
})
}

shareBtnEl.addEventListener('click', eventListener)
}

function createStopBtn (root) {
if (document.getElementById('stop-btn')) return
const stopBtn = document.createElement('button')
stopBtn.id = 'stop-btn'
stopBtn.className = 'btn btn-danger mt-3'
Expand All @@ -55,20 +61,20 @@ function createStopBtn (root) {
const stopBtnEl = document.getElementById('stop-btn')
stopBtnEl.addEventListener('click', async () => {
if (await isRunning()) {
const micPid = window.localStorage.getItem('micPid')
chrome.runtime.sendMessage({ messageName: MESSAGE_NAME, message: 'node-stopped', cmd: 'StopPipewireScreenAudio', args: [{ micPid }] })
const micId = window.localStorage.getItem('micId')
chrome.runtime.sendMessage({ messageName: MESSAGE_NAME, message: 'node-stopped', cmd: 'StopPipewireScreenAudio', args: [{ micId }] })
}
})
}

function createBlacklistBtn (root) {
if (document.getElementById('blacklist-btn')) return
const blacklistBtn = document.createElement('button')
blacklistBtn.id = 'blacklist-btn'
blacklistBtn.className = 'btn btn-danger px-3'
blacklistBtn.innerText = 'Hide'
root.appendChild(blacklistBtn)

blacklistBtnEl = document.getElementById('blacklist-btn')
blacklistBtn.addEventListener('click', async () => {
const nodesList = JSON.parse(window.localStorage.getItem('nodesList'))
const nodeToBlacklist = { name: nodesList.find(n => n.properties['object.serial'] === dropdown.value).properties['application.name'] }
Expand All @@ -88,7 +94,7 @@ function createBlacklistBtn (root) {

async function updateGui () {
if (await isRunning()) {
message.innerText = `Running with PID: ${window.localStorage.getItem('micPid')}`
message.innerText = `Running virtmic Id: ${window.localStorage.getItem('micId')}`
message.hidden = false
dropdown.hidden = true
createStopBtn(buttonGroup)
Expand Down Expand Up @@ -118,11 +124,6 @@ async function populateNodesList (response) {
whitelistedNodes = response.filter(node => !bnNames.includes(node.properties['application.name']))
}

// const allDesktopAudioOption = document.createElement('option')
// allDesktopAudioOption.innerText = ALL_DESKTOP_AUDIO_TEXT
// allDesktopAudioOption.value = ALL_DESKTOP_AUDIO_TEXT
// dropdown.appendChild(allDesktopAudioOption)

for (const element of whitelistedNodes) {
let text = element.properties['media.name']
if (element.properties['application.name']) {
Expand Down Expand Up @@ -157,19 +158,31 @@ async function populateNodesList (response) {
}
}

function checkVersionMatch (nativeVersion) {
const extVersionSplit = EXT_VERSION.split('.')
const nativeVersionSplit = nativeVersion.split('.')
return extVersionSplit[0] === nativeVersionSplit[0] && extVersionSplit[1] === nativeVersionSplit[1]
}

function onReload (response) {
populateNodesList(response)
updateGui()
}

function onResponse (response) {
function onResponse (response) {
if (!checkVersionMatch(response.version)) {
message.innerText = `Version mismatch\nExtension: ${EXT_VERSION}\nNative: ${response.version}`
dropdown.hidden = true
return
}
const settings = document.getElementById('settings')
settings.addEventListener('click', async () => {
window.open('settings.html')
})
setInterval(() => { chrome.runtime.sendNativeMessage(MESSAGE_NAME, { cmd: 'GetNodes', args: [] }).then(onReload, onError) }, 1000)
chrome.runtime.sendNativeMessage(MESSAGE_NAME, { cmd: 'GetNodes', args: [] }).then(onReload, onError)
nodesLoop = setInterval(() => { chrome.runtime.sendNativeMessage(MESSAGE_NAME, { cmd: 'GetNodes', args: [] }).then(onReload, onError) }, 1000)
window.localStorage.setItem('nodesList', null)
window.localStorage.setItem('selectedNode', null)
populateNodesList(response)
updateGui()
}

Expand All @@ -180,13 +193,15 @@ function onError (error) {
}

function handleMessage (message) {
if (message === 'pid-updated') {
if (message === 'mic-id-updated') {
const shareBtnEl = document.getElementById('share-btn')
const hideBtnEl = document.getElementById('blacklist-btn')
buttonGroup.removeChild(shareBtnEl)
buttonGroup.removeChild(hideBtnEl)
updateGui()
}

if (message === 'pid-removed') {
if (message === 'mic-id-removed') {
const stopBtnEl = document.getElementById('stop-btn')
buttonGroup.removeChild(stopBtnEl)
updateGui()
Expand All @@ -195,4 +210,5 @@ function handleMessage (message) {

chrome.runtime.onMessage.addListener(handleMessage)

chrome.runtime.sendNativeMessage(MESSAGE_NAME, { cmd: 'GetNodes', args: [] }).then(onResponse, onError)
chrome.runtime.sendNativeMessage(MESSAGE_NAME, { cmd: 'GetVersion', args: [] }).then(onResponse, onError)

8 changes: 8 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env bash

projectRoot="$( cd -- "$(dirname "$0")" > /dev/null 2>&1 ; pwd -P )"

mkdir -p ~/.mozilla/native-messaging-hosts

sed "s|/usr/lib/pipewire-screenaudio|$projectRoot/native|g" $projectRoot/native/native-messaging-hosts/firefox.json > ~/.mozilla/native-messaging-hosts/com.icedborn.pipewirescreenaudioconnector.json

4 changes: 0 additions & 4 deletions native/CMakeLists.txt

This file was deleted.

9 changes: 0 additions & 9 deletions native/build.sh

This file was deleted.

47 changes: 36 additions & 11 deletions native/connector/pipewire-screen-audio-connector.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,24 @@ function toMessage () {
echo -n "$message"
}

function GetVersion () {
toMessage '{"version":"0.2.0"}'
}

function GetNodes () {
local nodes=`pactl -f json list | jq '.sink_inputs' | jq -c '[{"properties": {"media.name": "[All Desktop Audio]", "application.name": "", "object.serial": -1}}] + [ .[] | select(.properties["media.class"] == "Stream/Output/Audio") ]'`
local nodes=`pw-dump | jq -c '
[{
"properties": {
"media.name": "[All Desktop Audio]",
"application.name": "",
"object.serial": -1
}
}] + [ .[] |
select(.info.props["media.class"] == "Stream/Output/Audio") |
.["properties"] = .info.props |
del(.info)
]
'`
toMessage "$nodes"
exit
}
Expand All @@ -37,33 +53,39 @@ function StartPipewireScreenAudio () {
local args="$1"

local node=`echo $args | jq -r '.[].node' | head -n 1`
echo $node | nohup $projectRoot/out/pipewire-screenaudio > /dev/null &
local micPid=$!

sleep 1
local micId=`pw-cli ls Node | grep -B 3 'pipewire-screenaudio' | grep 'object.serial' | awk '{print $3}' | jq -r`
nohup $projectRoot/connector/virtmic.sh $node >/dev/null 2>&1 &

nohup $projectRoot/connector/watcher.sh $micPid $micId > /dev/null &
sleep 1
local micId=`
pw-dump |
jq -c '[ .[] | select(.info.props["node.name"] == "pipewire-screenaudio") ][0].id'
`

toMessage '{"micPid":'$micPid',"micId":'$micId'}'
toMessage '{"micId":'$micId'}'
exit
}

function StopPipewireScreenAudio () {
local args="$1"
local micPid=`echo $args | jq '.[].micPid' | xargs | head -n 1`
local micId=`echo $args | jq '.[].micId' | xargs | head -n 1`

kill "$micPid" && toMessage '{"success":true}' && exit
if [ ! "`pw-cli info "$micId" 2>/dev/null | wc -l`" -eq "0" ]; then
[ "`pw-cli destroy "$micId" 2>&1 | wc -l`" -eq "0" ] &&
toMessage '{"success":true}' && exit
fi

toMessage '{"success":false}'
exit
}

function IsPipewireScreenAudioRunning () {
local args="$1"
local micPid=`echo $args | jq '.[].micPid' | xargs | head -n 1`
local micId=`echo $args | jq '.[].micId' | xargs | head -n 1`

(ps -p "$micPid" > /dev/null) && toMessage '{"isRunning":true}' && exit
if [ ! "`pw-cli info "$micId" 2>/dev/null | wc -l`" -eq "0" ]; then
toMessage '{"isRunning":true}' && exit
fi

toMessage '{"isRunning":false}'
exit
Expand All @@ -76,6 +98,9 @@ cmd=`echo "$payload" | jq -r .cmd`
args=`echo "$payload" | jq .args`

case $cmd in
'GetVersion')
GetVersion
;;
'GetNodes')
GetNodes "$args"
;;
Expand Down
Loading

0 comments on commit 2d9282d

Please sign in to comment.