Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a10cfda56d | ||
|
|
2a809e163e | ||
|
|
bc317d5531 | ||
|
|
b4db987217 | ||
|
|
9f46e710a9 | ||
|
|
d6641a7a6e | ||
|
|
b836be6530 | ||
|
|
bfb0714b13 | ||
|
|
374b854261 | ||
|
|
1f6105f76b | ||
|
|
3071159332 | ||
|
|
f2de080e1b | ||
|
|
f4a60f281d | ||
|
|
d693535d53 | ||
|
|
e41af697f7 | ||
|
|
b0a8815bb8 | ||
|
|
150fd4364e | ||
|
|
a0a2924796 | ||
|
|
3c48621427 | ||
|
|
9faabe1f3e | ||
|
|
68473d04d8 | ||
|
|
7c8f72b8d8 | ||
|
|
27cdd9f9a5 | ||
|
|
d184823ee9 | ||
|
|
dd2beed4eb | ||
|
|
3740553aba | ||
|
|
555f5c62f5 | ||
|
|
af3c62c263 | ||
|
|
c4f6c33f9d | ||
|
|
758ffa8cb5 | ||
|
|
753da8b1d3 | ||
|
|
5f37ab314f | ||
|
|
c7bff3f428 | ||
|
|
34e23f7187 | ||
|
|
c325dd6798 | ||
|
|
b42fde85d1 | ||
|
|
cc379ada85 | ||
|
|
fdb6145bbd | ||
|
|
8bd4d1896a | ||
|
|
1a2d09fe3d | ||
|
|
f01c941c90 | ||
|
|
95d391e869 | ||
|
|
af107168db | ||
|
|
e7e6c5d82b | ||
|
|
ee0c66762d | ||
|
|
c3483a42db | ||
|
|
d68797cebb | ||
|
|
ef221d4574 | ||
|
|
3fa2e3fa3c |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
/build
|
/build
|
||||||
|
.vscode
|
||||||
|
|||||||
@@ -13,10 +13,28 @@ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG")
|
|||||||
string(TIMESTAMP TIMESTAMP %s)
|
string(TIMESTAMP TIMESTAMP %s)
|
||||||
# set(CMAKE_AUTOUIC ON)
|
# set(CMAKE_AUTOUIC ON)
|
||||||
|
|
||||||
find_package(Qt5 CONFIG REQUIRED COMPONENTS
|
find_package(Qt5 COMPONENTS Widgets)
|
||||||
Widgets
|
if (Qt5_FOUND)
|
||||||
WebEngineWidgets
|
find_package(Qt5 CONFIG REQUIRED COMPONENTS Widgets WebEngineWidgets)
|
||||||
)
|
|
||||||
|
find_package(KF5Notifications)
|
||||||
|
if(KF5Notifications_FOUND)
|
||||||
|
add_definitions( -DKNOTIFICATIONS )
|
||||||
|
endif()
|
||||||
|
|
||||||
|
find_package(KF5XmlGui)
|
||||||
|
if(KF5XmlGui_FOUND)
|
||||||
|
add_definitions( -DKXMLGUI )
|
||||||
|
endif()
|
||||||
|
|
||||||
|
find_package(KF5GlobalAccel)
|
||||||
|
if(KF5GlobalAccel_FOUND)
|
||||||
|
add_definitions( -DKGLOBALACCEL )
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
message(WARNING "Qt 5 was not found on your system and Qt 6 will be used. You will not be able to use any features using KDE Frameworks.")
|
||||||
|
find_package(Qt6 CONFIG REQUIRED COMPONENTS Widgets WebEngineWidgets)
|
||||||
|
endif()
|
||||||
|
|
||||||
set(discord-screenaudio_SRC
|
set(discord-screenaudio_SRC
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
@@ -24,6 +42,7 @@ set(discord-screenaudio_SRC
|
|||||||
src/virtmic.cpp
|
src/virtmic.cpp
|
||||||
src/discordpage.cpp
|
src/discordpage.cpp
|
||||||
src/streamdialog.cpp
|
src/streamdialog.cpp
|
||||||
|
src/log.cpp
|
||||||
resources.qrc
|
resources.qrc
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -50,7 +69,18 @@ add_subdirectory(submodules/rohrkabel)
|
|||||||
|
|
||||||
add_executable(discord-screenaudio ${discord-screenaudio_SRC})
|
add_executable(discord-screenaudio ${discord-screenaudio_SRC})
|
||||||
|
|
||||||
target_link_libraries(discord-screenaudio Qt5::Widgets Qt5::WebEngineWidgets rohrkabel)
|
target_link_libraries(discord-screenaudio Qt::Widgets Qt::WebEngineWidgets rohrkabel)
|
||||||
|
|
||||||
|
if(KF5Notifications_FOUND)
|
||||||
|
target_link_libraries(discord-screenaudio KF5::Notifications)
|
||||||
|
install(FILES assets/discord-screenaudio.notifyrc DESTINATION ${CMAKE_INSTALL_PREFIX}/share/knotifications5)
|
||||||
|
endif()
|
||||||
|
if(KF5XmlGui_FOUND)
|
||||||
|
target_link_libraries(discord-screenaudio KF5::XmlGui)
|
||||||
|
endif()
|
||||||
|
if(KF5GlobalAccel_FOUND)
|
||||||
|
target_link_libraries(discord-screenaudio KF5::GlobalAccel)
|
||||||
|
endif()
|
||||||
|
|
||||||
install(TARGETS discord-screenaudio DESTINATION bin)
|
install(TARGETS discord-screenaudio DESTINATION bin)
|
||||||
install(FILES assets/de.shorsh.discord-screenaudio.png DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/256x256/apps)
|
install(FILES assets/de.shorsh.discord-screenaudio.png DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/256x256/apps)
|
||||||
|
|||||||
41
README.md
41
README.md
@@ -1,13 +1,17 @@
|
|||||||
# discord-screenaudio
|
# discord-screenaudio
|
||||||
|
|
||||||
A very WIP custom discord client that supports streaming with audio on Linux,
|
A custom discord client that supports streaming with audio on Linux, made
|
||||||
made possible by the
|
possible by the
|
||||||
[great work](https://github.com/edisionnano/Screenshare-with-audio-on-Discord-with-Linux)
|
[great work](https://github.com/edisionnano/Screenshare-with-audio-on-Discord-with-Linux)
|
||||||
of [@edisionnano](https://github.com/edisionnano) and the
|
of [@edisionnano](https://github.com/edisionnano) and the
|
||||||
[Rohrkabel library](https://github.com/Soundux/rohrkabel) by
|
[Rohrkabel library](https://github.com/Soundux/rohrkabel) by
|
||||||
[@Curve](https://github.com/Curve).
|
[@Curve](https://github.com/Curve).
|
||||||
|
|
||||||

|
Unlike a lot of other solutions, the audio here is directly fed into the
|
||||||
|
screenshare and not passed to the user microphone
|
||||||
|
([see explanation](#how-it-works)).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
The purpose of this project is **not** to provide an alternative to the original
|
The purpose of this project is **not** to provide an alternative to the original
|
||||||
Discord client. Rather, it should be used in addition to the original client in
|
Discord client. Rather, it should be used in addition to the original client in
|
||||||
@@ -15,31 +19,48 @@ case you want to stream something, maybe used with a second account. For
|
|||||||
anything else, this client has way too many things that work less well than in
|
anything else, this client has way too many things that work less well than in
|
||||||
the original client.
|
the original client.
|
||||||
|
|
||||||
|
Technically this could be against
|
||||||
|
[Discord's TOS](https://discord.com/terms#software-in-discord%E2%80%99s-services),
|
||||||
|
so be warned. Discord probably won't ban you for using this, but if they do I
|
||||||
|
told you and it's not my fault.
|
||||||
|
|
||||||
## Known Issues
|
## Known Issues
|
||||||
|
|
||||||
- Only works with **PipeWire**
|
- Only works with **PipeWire**
|
||||||
- Only works on **X11**
|
- Can only share primary screen on X11 (no other screens or specific
|
||||||
- Can only share primary screen (no other screens or specific applications) (see
|
applications) (see
|
||||||
[#1](https://github.com/maltejur/discord-screenaudio/issues/1))
|
[#1](https://github.com/maltejur/discord-screenaudio/issues/1))
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
If you are using Arch, you can build and install
|
You have multiple options:
|
||||||
[from the AUR](https://aur.archlinux.org/packages/discord-screenaudio). If you
|
|
||||||
aren't, you will have to [build it yourself](#building-from-source).
|
- Install the Flatpak (recommended):
|
||||||
|
`flatpak install de.shorsh.discord-screenaudio`
|
||||||
|
- If you are on Arch, you can build and install
|
||||||
|
[`discord-screenaudio`](https://aur.archlinux.org/packages/discord-screenaudio)
|
||||||
|
from the AUR
|
||||||
|
- If you are on openSUSE, you can use the
|
||||||
|
[Open Build Service package](https://software.opensuse.org//download.html?project=games%3Atools&package=discord-screenaudio)
|
||||||
|
by [@VortexAcherontic](https://github.com/VortexAcherontic)
|
||||||
|
- You can [build it yourself](#building-from-source)
|
||||||
|
|
||||||
## Building from Source
|
## Building from Source
|
||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
- Basic building tools
|
- Basic building tools
|
||||||
|
- An up-to-date system (I can't guarantee that it works on Debian or Ubuntu
|
||||||
|
20/21)
|
||||||
- CMake
|
- CMake
|
||||||
- Qt5 and QtWebEngine
|
- Qt5 and QtWebEngine
|
||||||
- **PipeWire** (it currently doesn't work with PulseAudio)
|
- **PipeWire** (it currently doesn't work with PulseAudio)
|
||||||
- Git
|
- Git
|
||||||
|
- _Kf5Notifications (optional, for better notifications)_
|
||||||
|
- _KXMLGui and KGlobalAccel (optional, for keybinds)_
|
||||||
|
|
||||||
On Debian:
|
With apt:
|
||||||
`apt install -y build-essential cmake qtbase5-dev qtwebengine5-dev pkg-config libpipewire-0.3-dev git`
|
`apt install -y build-essential cmake qtbase5-dev qtwebengine5-dev libkf5notifications-dev libkf5xmlgui-dev libkf5globalaccel-dev pkg-config libpipewire-0.3-dev git`
|
||||||
|
|
||||||
### Building
|
### Building
|
||||||
|
|
||||||
|
|||||||
@@ -2,5 +2,7 @@
|
|||||||
Type=Application
|
Type=Application
|
||||||
Name=discord-screenaudio
|
Name=discord-screenaudio
|
||||||
Exec=discord-screenaudio
|
Exec=discord-screenaudio
|
||||||
|
Comment=A custom discord client that supports streaming with audio on Linux.
|
||||||
Icon=de.shorsh.discord-screenaudio
|
Icon=de.shorsh.discord-screenaudio
|
||||||
|
Categories=Network;InstantMessaging;
|
||||||
Terminal=false
|
Terminal=false
|
||||||
|
|||||||
@@ -4,15 +4,23 @@
|
|||||||
<metadata_license>CC0-1.0</metadata_license>
|
<metadata_license>CC0-1.0</metadata_license>
|
||||||
<project_license>GPL-3.0+</project_license>
|
<project_license>GPL-3.0+</project_license>
|
||||||
<name>discord-screenaudio</name>
|
<name>discord-screenaudio</name>
|
||||||
|
<developer_name>Malte Jürgens</developer_name>
|
||||||
<releases>
|
<releases>
|
||||||
<release version="${DISCORD_SCEENAUDIO_VERSION_FULL}" timestamp="${TIMESTAMP}" />
|
<release version="${DISCORD_SCEENAUDIO_VERSION_FULL}" timestamp="${TIMESTAMP}" />
|
||||||
</releases>
|
</releases>
|
||||||
|
|
||||||
<summary>A very WIP custom discord client that supports streaming with audio on Linux</summary>
|
<summary>A custom discord client that supports streaming with audio on Linux</summary>
|
||||||
|
|
||||||
<description>
|
<description>
|
||||||
<p>A very WIP custom discord client that supports streaming with audio on Linux, made possible by the great work of @edisionnano and the Rohrkabel library by @Curve.</p>
|
<p>A custom discord client that supports streaming with audio on Linux, made possible by the great work of @edisionnano and the Rohrkabel library by @Curve.</p>
|
||||||
|
<p>Unlike a lot of other solutions, the audio here is directly fed into the screenshare and not passed to the user microphone.</p>
|
||||||
<p>The purpose of this project is not to provide an alternative to the original Discord client. Rather, it should be used in addition to the original client in case you want to stream something, maybe used with a second account. For anything else, this client has way too many things that work less well than in the original client.</p>
|
<p>The purpose of this project is not to provide an alternative to the original Discord client. Rather, it should be used in addition to the original client in case you want to stream something, maybe used with a second account. For anything else, this client has way too many things that work less well than in the original client.</p>
|
||||||
|
<p>Technically this could be against Discord's TOS, so be warned. Discord probably won't ban you for using this, but if they do I told you and it's not my fault.</p>
|
||||||
|
<p>Known issues:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Only works with PipeWire</li>
|
||||||
|
<li>Can only share primary screen on X11</li>
|
||||||
|
</ul>
|
||||||
</description>
|
</description>
|
||||||
|
|
||||||
<launchable type="desktop-id">
|
<launchable type="desktop-id">
|
||||||
@@ -22,7 +30,7 @@
|
|||||||
<screenshots>
|
<screenshots>
|
||||||
<screenshot type="default">
|
<screenshot type="default">
|
||||||
<image>
|
<image>
|
||||||
https://user-images.githubusercontent.com/48161361/179571245-11ea05f3-fb5e-4aef-9132-2736e122ef04.png
|
https://user-images.githubusercontent.com/48161361/192137080-33466cf7-8c56-4373-90c6-01ea74b6fb83.png
|
||||||
</image>
|
</image>
|
||||||
</screenshot>
|
</screenshot>
|
||||||
</screenshots>
|
</screenshots>
|
||||||
|
|||||||
8
assets/discord-screenaudio.notifyrc
Normal file
8
assets/discord-screenaudio.notifyrc
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[Global]
|
||||||
|
IconName=de.shorsh.discord-screenaudio
|
||||||
|
Name=discord-screenaudio
|
||||||
|
DesktopEntry=de.shorsh.discord-screenaudio.desktop
|
||||||
|
|
||||||
|
[Event/discordNotification]
|
||||||
|
Name=Discord Notification
|
||||||
|
Action=Popup
|
||||||
@@ -3,16 +3,29 @@
|
|||||||
navigator.mediaDevices.chromiumGetDisplayMedia =
|
navigator.mediaDevices.chromiumGetDisplayMedia =
|
||||||
navigator.mediaDevices.getDisplayMedia;
|
navigator.mediaDevices.getDisplayMedia;
|
||||||
|
|
||||||
|
function sleep(ms) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
const getAudioDevice = async (nameOfAudioDevice) => {
|
const getAudioDevice = async (nameOfAudioDevice) => {
|
||||||
await navigator.mediaDevices.getUserMedia({
|
await navigator.mediaDevices.getUserMedia({
|
||||||
audio: true,
|
audio: true,
|
||||||
});
|
});
|
||||||
|
let audioDevice;
|
||||||
|
while (audioDevice === undefined) {
|
||||||
let devices = await navigator.mediaDevices.enumerateDevices();
|
let devices = await navigator.mediaDevices.enumerateDevices();
|
||||||
let audioDevice = devices.find(({ label }) => label === nameOfAudioDevice);
|
audioDevice = devices.find(({ label }) => label === nameOfAudioDevice);
|
||||||
|
if (!audioDevice)
|
||||||
|
console.log(
|
||||||
|
`dsa: Did not find '${nameOfAudioDevice}', trying again in 100ms`
|
||||||
|
);
|
||||||
|
await sleep(100);
|
||||||
|
}
|
||||||
|
console.log(`dsa: Found '${nameOfAudioDevice}'`);
|
||||||
return audioDevice;
|
return audioDevice;
|
||||||
};
|
};
|
||||||
|
|
||||||
function setGetDisplayMedia(overrideArgs = undefined) {
|
function setGetDisplayMedia(video = true, overrideArgs = undefined) {
|
||||||
const getDisplayMedia = async (...args) => {
|
const getDisplayMedia = async (...args) => {
|
||||||
var id;
|
var id;
|
||||||
try {
|
try {
|
||||||
@@ -50,6 +63,7 @@ function setGetDisplayMedia(overrideArgs = undefined) {
|
|||||||
: args || [{ video: true, audio: true }])
|
: args || [{ video: true, audio: true }])
|
||||||
);
|
);
|
||||||
gdm.addTrack(track);
|
gdm.addTrack(track);
|
||||||
|
if (!video) for (const track of gdm.getVideoTracks()) track.enabled = false;
|
||||||
return gdm;
|
return gdm;
|
||||||
};
|
};
|
||||||
navigator.mediaDevices.getDisplayMedia = getDisplayMedia;
|
navigator.mediaDevices.getDisplayMedia = getDisplayMedia;
|
||||||
@@ -61,6 +75,52 @@ const clonedElements = [];
|
|||||||
const hiddenElements = [];
|
const hiddenElements = [];
|
||||||
let wasStreamActive = false;
|
let wasStreamActive = false;
|
||||||
|
|
||||||
|
function createButton(text, onClick) {
|
||||||
|
const button = document.createElement("button");
|
||||||
|
button.style.marginBottom = "20px";
|
||||||
|
button.classList =
|
||||||
|
"button-f2h6uQ lookFilled-yCfaCM colorBrand-I6CyqQ sizeSmall-wU2dO- grow-2sR_-F";
|
||||||
|
button.innerText = text;
|
||||||
|
button.addEventListener("click", onClick);
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSwitch(text, enabled, onClick) {
|
||||||
|
const container = document.createElement("div");
|
||||||
|
container.style.marginBottom = "20px";
|
||||||
|
container.className = "labelRow-NnoUIp";
|
||||||
|
|
||||||
|
const label = document.createElement("label");
|
||||||
|
label.innerText = text;
|
||||||
|
label.className = "title-2yADjX";
|
||||||
|
container.appendChild(label);
|
||||||
|
|
||||||
|
const svg = document.createElement("div");
|
||||||
|
container.appendChild(svg);
|
||||||
|
|
||||||
|
function setSvgDisabled() {
|
||||||
|
svg.innerHTML = `<div class="container-1QtPKm default-colors disabled-3_3z1m" style="opacity: 0.3; background-color: rgb(114, 118, 125);"><svg class="slider-HJFN2i" viewBox="0 0 28 20" preserveAspectRatio="xMinYMid meet" aria-hidden="true" style="left: -3px;"><rect fill="white" x="4" y="0" height="20" width="20" rx="10"></rect><svg viewBox="0 0 20 20" fill="none"><path fill="rgba(114, 118, 125, 1)" d="M5.13231 6.72963L6.7233 5.13864L14.855 13.2704L13.264 14.8614L5.13231 6.72963Z"></path><path fill="rgba(114, 118, 125, 1)" d="M13.2704 5.13864L14.8614 6.72963L6.72963 14.8614L5.13864 13.2704L13.2704 5.13864Z"></path></svg></svg><input id="uid_75" type="checkbox" class="input-125oad" tabindex="-1" disabled=""></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSvgEnabled() {
|
||||||
|
svg.innerHTML = `<div class="container-1QtPKm default-colors checked-16gMAN" style="opacity: 1; background-color: rgb(59, 165, 92);"><svg class="slider-HJFN2i" viewBox="0 0 28 20" preserveAspectRatio="xMinYMid meet" aria-hidden="true" style="left: 12px;"><rect fill="white" x="4" y="0" height="20" width="20" rx="10"></rect><svg viewBox="0 0 20 20" fill="none"><path fill="rgba(59, 165, 92, 1)" d="M7.89561 14.8538L6.30462 13.2629L14.3099 5.25755L15.9009 6.84854L7.89561 14.8538Z"></path><path fill="rgba(59, 165, 92, 1)" d="M4.08643 11.0903L5.67742 9.49929L9.4485 13.2704L7.85751 14.8614L4.08643 11.0903Z"></path></svg></svg><input id="uid_74" type="checkbox" class="input-125oad" tabindex="0" checked=""></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSvg() {
|
||||||
|
if (enabled) setSvgEnabled();
|
||||||
|
else setSvgDisabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
container.addEventListener("click", () => {
|
||||||
|
enabled = !enabled;
|
||||||
|
updateSvg();
|
||||||
|
onClick(enabled);
|
||||||
|
});
|
||||||
|
updateSvg();
|
||||||
|
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
const streamActive =
|
const streamActive =
|
||||||
document.getElementsByClassName("panel-2ZFCRb activityPanel-9icbyU")
|
document.getElementsByClassName("panel-2ZFCRb activityPanel-9icbyU")
|
||||||
@@ -98,8 +158,16 @@ setInterval(() => {
|
|||||||
|
|
||||||
const initialDisplay = el.style.display;
|
const initialDisplay = el.style.display;
|
||||||
|
|
||||||
window.discordScreenaudioStartStream = (width, height, frameRate) => {
|
window.discordScreenaudioStartStream = (
|
||||||
setGetDisplayMedia({
|
video,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
frameRate
|
||||||
|
) => {
|
||||||
|
window.discordScreenaudioResolutionString = video
|
||||||
|
? `${height}p ${frameRate}FPS`
|
||||||
|
: "Audio Only";
|
||||||
|
setGetDisplayMedia(video, {
|
||||||
audio: true,
|
audio: true,
|
||||||
video: { width, height, frameRate },
|
video: { width, height, frameRate },
|
||||||
});
|
});
|
||||||
@@ -121,12 +189,21 @@ setInterval(() => {
|
|||||||
document.getElementsByClassName("dirscordScreenaudioAboutText").length == 0
|
document.getElementsByClassName("dirscordScreenaudioAboutText").length == 0
|
||||||
) {
|
) {
|
||||||
for (const el of document.getElementsByClassName("info-3pQQBb")) {
|
for (const el of document.getElementsByClassName("info-3pQQBb")) {
|
||||||
const aboutEl = document.createElement("div");
|
let aboutEl;
|
||||||
|
if (window.discordScreenaudioKXMLGUI) {
|
||||||
|
aboutEl = document.createElement("a");
|
||||||
|
aboutEl.addEventListener("click", () => {
|
||||||
|
console.log("!discord-screenaudio-about");
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
aboutEl = document.createElement("div");
|
||||||
|
}
|
||||||
aboutEl.innerText = `discord-screenaudio ${window.discordScreenaudioVersion}`;
|
aboutEl.innerText = `discord-screenaudio ${window.discordScreenaudioVersion}`;
|
||||||
aboutEl.style.fontSize = "12px";
|
aboutEl.style.fontSize = "12px";
|
||||||
aboutEl.style.color = "var(--text-muted)";
|
aboutEl.style.color = "var(--text-muted)";
|
||||||
aboutEl.style.textTransform = "none";
|
aboutEl.style.textTransform = "none";
|
||||||
aboutEl.classList.add("dirscordScreenaudioAboutText");
|
aboutEl.classList.add("dirscordScreenaudioAboutText");
|
||||||
|
aboutEl.style.cursor = "pointer";
|
||||||
el.appendChild(aboutEl);
|
el.appendChild(aboutEl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,6 +211,105 @@ setInterval(() => {
|
|||||||
// Remove stream settings if stream is active
|
// Remove stream settings if stream is active
|
||||||
document.getElementById("manage-streams-change-windows")?.remove();
|
document.getElementById("manage-streams-change-windows")?.remove();
|
||||||
document.querySelector(`[aria-label="Stream Settings"]`)?.remove();
|
document.querySelector(`[aria-label="Stream Settings"]`)?.remove();
|
||||||
|
|
||||||
|
// Add event listener for keybind tab
|
||||||
|
if (
|
||||||
|
document
|
||||||
|
.getElementById("keybinds-tab")
|
||||||
|
?.getElementsByClassName(
|
||||||
|
"container-3jbRo5 info-1hMolH browserNotice-1u-Y5o"
|
||||||
|
).length
|
||||||
|
) {
|
||||||
|
const el = document
|
||||||
|
.getElementById("keybinds-tab")
|
||||||
|
.getElementsByClassName("children-1xdcWE")[0];
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.style.marginBottom = "50px";
|
||||||
|
div.appendChild(
|
||||||
|
createButton("Edit Global Keybinds", () => {
|
||||||
|
console.log("!discord-screenaudio-keybinds");
|
||||||
|
})
|
||||||
|
);
|
||||||
|
el.innerHTML = "";
|
||||||
|
el.appendChild(div);
|
||||||
|
}
|
||||||
|
|
||||||
|
const buttonContainer =
|
||||||
|
document.getElementsByClassName("container-YkUktl")[0];
|
||||||
|
if (!buttonContainer) {
|
||||||
|
console.log(
|
||||||
|
"dsa: Cannot locate Mute/Deafen/Settings button container, please report this on GitHub"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const muteBtn = buttonContainer
|
||||||
|
? buttonContainer.getElementsByClassName(
|
||||||
|
"button-12Fmur enabled-9OeuTA button-f2h6uQ lookBlank-21BCro colorBrand-I6CyqQ grow-2sR_-F"
|
||||||
|
)[0]
|
||||||
|
: null;
|
||||||
|
window.discordScreenaudioToggleMute = () => muteBtn && muteBtn.click();
|
||||||
|
|
||||||
|
const deafenBtn = buttonContainer
|
||||||
|
? buttonContainer.getElementsByClassName(
|
||||||
|
"button-12Fmur enabled-9OeuTA button-f2h6uQ lookBlank-21BCro colorBrand-I6CyqQ grow-2sR_-F"
|
||||||
|
)[1]
|
||||||
|
: null;
|
||||||
|
|
||||||
|
window.discordScreenaudioToggleDeafen = () => deafenBtn && deafenBtn.click();
|
||||||
|
|
||||||
|
if (window.discordScreenaudioResolutionString) {
|
||||||
|
for (const el of document.getElementsByClassName(
|
||||||
|
"qualityIndicator-39wQDy"
|
||||||
|
)) {
|
||||||
|
el.innerHTML = window.discordScreenaudioResolutionString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const accountTab = document.getElementById("my-account-tab");
|
||||||
|
if (accountTab) {
|
||||||
|
const discordScreenaudioSettings = document.getElementById(
|
||||||
|
"discord-screenaudio-settings"
|
||||||
|
);
|
||||||
|
if (!discordScreenaudioSettings) {
|
||||||
|
const firstDivider = accountTab.getElementsByClassName(
|
||||||
|
"divider-3nqZNm marginTop40-Q4o1tS"
|
||||||
|
)[0];
|
||||||
|
if (firstDivider) {
|
||||||
|
const section = document.createElement("div");
|
||||||
|
section.className = "marginTop40-Q4o1tS";
|
||||||
|
section.id = "discord-screenaudio-settings";
|
||||||
|
|
||||||
|
const title = document.createElement("h2");
|
||||||
|
title.className =
|
||||||
|
"h1-3iMExa title-lXcL8p defaultColor-3Olr-9 defaultMarginh1-1UYutH";
|
||||||
|
title.innerText = "discord-screenaudio";
|
||||||
|
section.appendChild(title);
|
||||||
|
|
||||||
|
section.appendChild(
|
||||||
|
createButton("Edit Global Keybinds", () => {
|
||||||
|
console.log("!discord-screenaudio-keybinds");
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
section.appendChild(
|
||||||
|
createSwitch(
|
||||||
|
"Move discord-screenaudio to the system tray instead of closing",
|
||||||
|
window.discordScreenaudioTrayEnabled,
|
||||||
|
(enabled) => {
|
||||||
|
window.discordScreenaudioTrayEnabled = enabled;
|
||||||
|
console.log(`!discord-screenaudio-tray-${enabled}`);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const divider = document.createElement("div");
|
||||||
|
divider.className = "divider-3nqZNm marginTop40-Q4o1tS";
|
||||||
|
|
||||||
|
firstDivider.after(section);
|
||||||
|
section.after(divider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
// Fix for broken discord notifications after restart
|
// Fix for broken discord notifications after restart
|
||||||
|
|||||||
@@ -1,9 +1,26 @@
|
|||||||
#include "discordpage.h"
|
#include "discordpage.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "mainwindow.h"
|
||||||
#include "virtmic.h"
|
#include "virtmic.h"
|
||||||
|
|
||||||
|
#ifdef KXMLGUI
|
||||||
|
#include <KAboutData>
|
||||||
|
#include <KHelpMenu>
|
||||||
|
#include <KShortcutsDialog>
|
||||||
|
#include <KXmlGuiWindow>
|
||||||
|
#include <QAction>
|
||||||
|
|
||||||
|
#ifdef KGLOBALACCEL
|
||||||
|
#include <KGlobalAccel>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QNetworkReply>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QWebChannel>
|
#include <QWebChannel>
|
||||||
#include <QWebEngineScript>
|
#include <QWebEngineScript>
|
||||||
@@ -32,53 +49,101 @@ DiscordPage::DiscordPage(QWidget *parent) : QWebEnginePage(parent) {
|
|||||||
settings()->setAttribute(QWebEngineSettings::PlaybackRequiresUserGesture,
|
settings()->setAttribute(QWebEngineSettings::PlaybackRequiresUserGesture,
|
||||||
false);
|
false);
|
||||||
settings()->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, false);
|
settings()->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, false);
|
||||||
|
settings()->setAttribute(QWebEngineSettings::ScrollAnimatorEnabled, true);
|
||||||
|
|
||||||
setUrl(QUrl("https://discord.com/app"));
|
setUrl(QUrl("https://discord.com/app"));
|
||||||
|
|
||||||
injectScript(":/assets/userscript.js");
|
injectScriptFile("userscript.js", ":/assets/userscript.js");
|
||||||
injectVersion(QApplication::applicationVersion());
|
|
||||||
|
injectScriptText("vars.js",
|
||||||
|
QString("window.discordScreenaudioVersion = '%1'; "
|
||||||
|
"window.discordScreenaudioTrayEnabled = %2;")
|
||||||
|
.arg(QApplication::applicationVersion())
|
||||||
|
.arg(MainWindow::instance()
|
||||||
|
->settings()
|
||||||
|
->value("trayIcon", false)
|
||||||
|
.toBool()));
|
||||||
|
|
||||||
|
#ifdef KXMLGUI
|
||||||
|
injectScriptText("xmlgui.js", "window.discordScreenaudioKXMLGUI = true;");
|
||||||
|
|
||||||
|
KAboutData aboutData(
|
||||||
|
"discord-screenaudio", "discord-screenaudio",
|
||||||
|
QApplication::applicationVersion(),
|
||||||
|
"Custom Discord client with the ability to stream audio on Linux",
|
||||||
|
KAboutLicense::GPL_V3, "Copyright 2022 (C) Malte Jürgens");
|
||||||
|
aboutData.setBugAddress("https://github.com/maltejur/discord-screenaudio");
|
||||||
|
aboutData.addAuthor("Malte Jürgens", "Author", "maltejur@dismail.de",
|
||||||
|
"https://github.com/maltejur");
|
||||||
|
aboutData.addCredit("edisionnano",
|
||||||
|
"For creating and documenting the approach for streaming "
|
||||||
|
"audio in Discord used in this project.",
|
||||||
|
QString(),
|
||||||
|
"https://github.com/edisionnano/"
|
||||||
|
"Screenshare-with-audio-on-Discord-with-Linux");
|
||||||
|
aboutData.addCredit(
|
||||||
|
"Curve", "For creating the Rohrkabel library used in this project.",
|
||||||
|
QString(), "https://github.com/Curve");
|
||||||
|
aboutData.addComponent("Rohrkabel", "A C++ RAII Pipewire-API Wrapper", "1.3",
|
||||||
|
"https://github.com/Soundux/rohrkabel");
|
||||||
|
m_helpMenu = new KHelpMenu(parent, aboutData);
|
||||||
|
|
||||||
|
#ifdef KGLOBALACCEL
|
||||||
|
injectScriptText("kglobalaccel.js",
|
||||||
|
"window.discordScreenaudioKGLOBALACCEL = true;");
|
||||||
|
|
||||||
|
auto toggleMuteAction = new QAction(this);
|
||||||
|
toggleMuteAction->setText("Toggle Mute");
|
||||||
|
toggleMuteAction->setIcon(QIcon::fromTheme("microphone-sensitivity-muted"));
|
||||||
|
connect(toggleMuteAction, &QAction::triggered, this,
|
||||||
|
&DiscordPage::toggleMute);
|
||||||
|
|
||||||
|
auto toggleDeafenAction = new QAction(this);
|
||||||
|
toggleDeafenAction->setText("Toggle Deafen");
|
||||||
|
toggleDeafenAction->setIcon(QIcon::fromTheme("audio-volume-muted"));
|
||||||
|
connect(toggleDeafenAction, &QAction::triggered, this,
|
||||||
|
&DiscordPage::toggleDeafen);
|
||||||
|
|
||||||
|
m_actionCollection = new KActionCollection(this);
|
||||||
|
m_actionCollection->addAction("toggleMute", toggleMuteAction);
|
||||||
|
KGlobalAccel::setGlobalShortcut(toggleMuteAction, QList<QKeySequence>{});
|
||||||
|
m_actionCollection->addAction("toggleDeafen", toggleDeafenAction);
|
||||||
|
KGlobalAccel::setGlobalShortcut(toggleDeafenAction, QList<QKeySequence>{});
|
||||||
|
|
||||||
|
m_shortcutsDialog = new KShortcutsDialog(KShortcutsEditor::GlobalAction);
|
||||||
|
m_shortcutsDialog->addCollection(m_actionCollection);
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
connect(&m_streamDialog, &StreamDialog::requestedStreamStart, this,
|
connect(&m_streamDialog, &StreamDialog::requestedStreamStart, this,
|
||||||
&DiscordPage::startStream);
|
&DiscordPage::startStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DiscordPage::injectScript(QString source) {
|
void DiscordPage::injectScriptText(QString name, QString content) {
|
||||||
qDebug() << "[main ] Injecting " << source;
|
qDebug(mainLog) << "Injecting " << name;
|
||||||
|
|
||||||
QFile userscript(source);
|
QWebEngineScript script;
|
||||||
|
|
||||||
if (!userscript.open(QIODevice::ReadOnly)) {
|
script.setSourceCode(content);
|
||||||
|
script.setName(name);
|
||||||
|
script.setWorldId(QWebEngineScript::MainWorld);
|
||||||
|
script.setInjectionPoint(QWebEngineScript::DocumentCreation);
|
||||||
|
script.setRunsOnSubFrames(false);
|
||||||
|
|
||||||
|
scripts().insert(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DiscordPage::injectScriptFile(QString name, QString source) {
|
||||||
|
QFile file(source);
|
||||||
|
|
||||||
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
qFatal("Failed to load %s with error: %s", source.toLatin1().constData(),
|
qFatal("Failed to load %s with error: %s", source.toLatin1().constData(),
|
||||||
userscript.errorString().toLatin1().constData());
|
file.errorString().toLatin1().constData());
|
||||||
} else {
|
} else {
|
||||||
QByteArray userscriptJs = userscript.readAll();
|
injectScriptText(name, file.readAll());
|
||||||
|
|
||||||
QWebEngineScript script;
|
|
||||||
|
|
||||||
script.setSourceCode(userscriptJs);
|
|
||||||
script.setName("userscript.js");
|
|
||||||
script.setWorldId(QWebEngineScript::MainWorld);
|
|
||||||
script.setInjectionPoint(QWebEngineScript::DocumentCreation);
|
|
||||||
script.setRunsOnSubFrames(false);
|
|
||||||
|
|
||||||
scripts().insert(script);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DiscordPage::injectVersion(QString version) {
|
|
||||||
QWebEngineScript script;
|
|
||||||
|
|
||||||
auto code = QString("window.discordScreenaudioVersion = '%1';").arg(version);
|
|
||||||
|
|
||||||
script.setSourceCode(code);
|
|
||||||
script.setName("version.js");
|
|
||||||
script.setWorldId(QWebEngineScript::MainWorld);
|
|
||||||
script.setInjectionPoint(QWebEngineScript::DocumentCreation);
|
|
||||||
script.setRunsOnSubFrames(false);
|
|
||||||
|
|
||||||
scripts().insert(script);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DiscordPage::featurePermissionRequested(const QUrl &securityOrigin,
|
void DiscordPage::featurePermissionRequested(const QUrl &securityOrigin,
|
||||||
QWebEnginePage::Feature feature) {
|
QWebEnginePage::Feature feature) {
|
||||||
// Allow every permission asked
|
// Allow every permission asked
|
||||||
@@ -87,7 +152,7 @@ void DiscordPage::featurePermissionRequested(const QUrl &securityOrigin,
|
|||||||
|
|
||||||
if (feature == QWebEnginePage::Feature::MediaAudioCapture) {
|
if (feature == QWebEnginePage::Feature::MediaAudioCapture) {
|
||||||
if (m_virtmicProcess.state() == QProcess::NotRunning) {
|
if (m_virtmicProcess.state() == QProcess::NotRunning) {
|
||||||
qDebug() << "[virtmic] Starting Virtmic with no target to make sure "
|
qDebug(virtmicLog) << "Starting Virtmic with no target to make sure "
|
||||||
"Discord can find all the audio devices";
|
"Discord can find all the audio devices";
|
||||||
m_virtmicProcess.start(QApplication::arguments()[0],
|
m_virtmicProcess.start(QApplication::arguments()[0],
|
||||||
{"--virtmic", "None"});
|
{"--virtmic", "None"});
|
||||||
@@ -119,18 +184,16 @@ QWebEnginePage *DiscordPage::createWindow(QWebEnginePage::WebWindowType type) {
|
|||||||
|
|
||||||
void DiscordPage::stopVirtmic() {
|
void DiscordPage::stopVirtmic() {
|
||||||
if (m_virtmicProcess.state() == QProcess::Running) {
|
if (m_virtmicProcess.state() == QProcess::Running) {
|
||||||
qDebug() << "[virtmic] Stopping Virtmic";
|
qDebug(virtmicLog) << "Stopping Virtmic";
|
||||||
m_virtmicProcess.kill();
|
m_virtmicProcess.kill();
|
||||||
m_virtmicProcess.waitForFinished();
|
m_virtmicProcess.waitForFinished();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DiscordPage::startVirtmic(QString target) {
|
void DiscordPage::startVirtmic(QString target) {
|
||||||
if (target != "None") {
|
qDebug(virtmicLog) << "Starting Virtmic with target" << target;
|
||||||
qDebug() << "[virtmic] Starting Virtmic with target" << target;
|
|
||||||
m_virtmicProcess.start(QApplication::arguments()[0], {"--virtmic", target});
|
m_virtmicProcess.start(QApplication::arguments()[0], {"--virtmic", target});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void DiscordPage::javaScriptConsoleMessage(
|
void DiscordPage::javaScriptConsoleMessage(
|
||||||
QWebEnginePage::JavaScriptConsoleMessageLevel level, const QString &message,
|
QWebEnginePage::JavaScriptConsoleMessageLevel level, const QString &message,
|
||||||
@@ -143,20 +206,58 @@ void DiscordPage::javaScriptConsoleMessage(
|
|||||||
m_streamDialog.updateTargets();
|
m_streamDialog.updateTargets();
|
||||||
} else if (message == "!discord-screenaudio-stream-stopped") {
|
} else if (message == "!discord-screenaudio-stream-stopped") {
|
||||||
stopVirtmic();
|
stopVirtmic();
|
||||||
|
} else if (message == "!discord-screenaudio-about") {
|
||||||
|
#ifdef KXMLGUI
|
||||||
|
m_helpMenu->aboutApplication();
|
||||||
|
#endif
|
||||||
|
} else if (message == "!discord-screenaudio-keybinds") {
|
||||||
|
#ifdef KXMLGUI
|
||||||
|
#ifdef KGLOBALACCEL
|
||||||
|
m_shortcutsDialog->show();
|
||||||
|
#else
|
||||||
|
QMessageBox::information(MainWindow::instance(), "discord-screenaudio",
|
||||||
|
"Keybinds are not supported on this platform "
|
||||||
|
"(KGlobalAccel is not available).",
|
||||||
|
QMessageBox::Ok);
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
QMessageBox::information(MainWindow::instance(), "discord-screenaudio",
|
||||||
|
"Keybinds are not supported on this platform "
|
||||||
|
"(KXmlGui and KGlobalAccel are not available).",
|
||||||
|
QMessageBox::Ok);
|
||||||
|
#endif
|
||||||
|
} else if (message == "!discord-screenaudio-tray-true") {
|
||||||
|
MainWindow::instance()->setTrayIcon(true);
|
||||||
|
} else if (message == "!discord-screenaudio-tray-false") {
|
||||||
|
MainWindow::instance()->setTrayIcon(false);
|
||||||
|
} else if (message.startsWith("dsa: ")) {
|
||||||
|
qDebug(userscriptLog) << message.mid(5).toUtf8().constData();
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "[discord]" << message;
|
qDebug(discordLog) << message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DiscordPage::startStream(QString target, uint width, uint height,
|
void DiscordPage::startStream(bool video, bool audio, uint width, uint height,
|
||||||
uint frameRate) {
|
uint frameRate, QString target) {
|
||||||
stopVirtmic();
|
stopVirtmic();
|
||||||
startVirtmic(target);
|
startVirtmic(audio ? target : "[None]");
|
||||||
// Wait a bit for the virtmic to start
|
// Wait a bit for the virtmic to start
|
||||||
QTimer::singleShot(target == "None" ? 0 : 200, [=]() {
|
QTimer::singleShot(200, [=]() {
|
||||||
runJavaScript(QString("window.discordScreenaudioStartStream(%1, %2, %3);")
|
runJavaScript(
|
||||||
.arg(width)
|
QString("window.discordScreenaudioStartStream(%1, %2, %3, %4);")
|
||||||
.arg(height)
|
.arg(video)
|
||||||
.arg(frameRate));
|
.arg(video ? width : 32)
|
||||||
|
.arg(video ? height : 16)
|
||||||
|
.arg(video ? frameRate : 1));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DiscordPage::toggleMute() {
|
||||||
|
qDebug(shortcutLog) << "Toggling mute";
|
||||||
|
runJavaScript("window.discordScreenaudioToggleMute();");
|
||||||
|
}
|
||||||
|
|
||||||
|
void DiscordPage::toggleDeafen() {
|
||||||
|
qDebug(shortcutLog) << "Toggling deafen";
|
||||||
|
runJavaScript("window.discordScreenaudioToggleDeafen();");
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,12 @@
|
|||||||
#include "streamdialog.h"
|
#include "streamdialog.h"
|
||||||
#include "virtmic.h"
|
#include "virtmic.h"
|
||||||
|
|
||||||
|
#ifdef KXMLGUI
|
||||||
|
#include <KActionCollection>
|
||||||
|
#include <KHelpMenu>
|
||||||
|
#include <KShortcutsDialog>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
#include <QWebEngineFullScreenRequest>
|
#include <QWebEngineFullScreenRequest>
|
||||||
#include <QWebEnginePage>
|
#include <QWebEnginePage>
|
||||||
@@ -16,6 +22,13 @@ public:
|
|||||||
private:
|
private:
|
||||||
StreamDialog m_streamDialog;
|
StreamDialog m_streamDialog;
|
||||||
QProcess m_virtmicProcess;
|
QProcess m_virtmicProcess;
|
||||||
|
#ifdef KXMLGUI
|
||||||
|
KHelpMenu *m_helpMenu;
|
||||||
|
#ifdef KGLOBALACCEL
|
||||||
|
KActionCollection *m_actionCollection;
|
||||||
|
KShortcutsDialog *m_shortcutsDialog;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
bool acceptNavigationRequest(const QUrl &url,
|
bool acceptNavigationRequest(const QUrl &url,
|
||||||
QWebEnginePage::NavigationType type,
|
QWebEnginePage::NavigationType type,
|
||||||
bool isMainFrame) override;
|
bool isMainFrame) override;
|
||||||
@@ -24,15 +37,18 @@ private:
|
|||||||
javaScriptConsoleMessage(QWebEnginePage::JavaScriptConsoleMessageLevel level,
|
javaScriptConsoleMessage(QWebEnginePage::JavaScriptConsoleMessageLevel level,
|
||||||
const QString &message, int lineNumber,
|
const QString &message, int lineNumber,
|
||||||
const QString &sourceID) override;
|
const QString &sourceID) override;
|
||||||
void injectScript(QString source);
|
void injectScriptText(QString name, QString content);
|
||||||
void injectVersion(QString version);
|
void injectScriptFile(QString name, QString source);
|
||||||
void stopVirtmic();
|
void stopVirtmic();
|
||||||
void startVirtmic(QString target);
|
void startVirtmic(QString target);
|
||||||
|
void toggleMute();
|
||||||
|
void toggleDeafen();
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void featurePermissionRequested(const QUrl &securityOrigin,
|
void featurePermissionRequested(const QUrl &securityOrigin,
|
||||||
QWebEnginePage::Feature feature);
|
QWebEnginePage::Feature feature);
|
||||||
void startStream(QString target, uint width, uint height, uint frameRate);
|
void startStream(bool video, bool audio, uint width, uint height,
|
||||||
|
uint frameRate, QString target);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Will immediately get destroyed again but is needed for navigation to
|
// Will immediately get destroyed again but is needed for navigation to
|
||||||
|
|||||||
7
src/log.cpp
Normal file
7
src/log.cpp
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
Q_LOGGING_CATEGORY(mainLog, "main");
|
||||||
|
Q_LOGGING_CATEGORY(discordLog, "discord");
|
||||||
|
Q_LOGGING_CATEGORY(userscriptLog, "userscript");
|
||||||
|
Q_LOGGING_CATEGORY(virtmicLog, "virtmic");
|
||||||
|
Q_LOGGING_CATEGORY(shortcutLog, "shortcut");
|
||||||
9
src/log.h
Normal file
9
src/log.h
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QLoggingCategory>
|
||||||
|
|
||||||
|
Q_DECLARE_LOGGING_CATEGORY(mainLog);
|
||||||
|
Q_DECLARE_LOGGING_CATEGORY(discordLog);
|
||||||
|
Q_DECLARE_LOGGING_CATEGORY(userscriptLog);
|
||||||
|
Q_DECLARE_LOGGING_CATEGORY(virtmicLog);
|
||||||
|
Q_DECLARE_LOGGING_CATEGORY(shortcutLog);
|
||||||
30
src/main.cpp
30
src/main.cpp
@@ -1,8 +1,13 @@
|
|||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "virtmic.h"
|
#include "virtmic.h"
|
||||||
|
|
||||||
|
#ifdef KXMLGUI
|
||||||
|
#include <KAboutData>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QCommandLineParser>
|
#include <QCommandLineParser>
|
||||||
|
#include <QLoggingCategory>
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
QApplication app(argc, argv);
|
QApplication app(argc, argv);
|
||||||
@@ -10,6 +15,9 @@ int main(int argc, char *argv[]) {
|
|||||||
QApplication::setWindowIcon(
|
QApplication::setWindowIcon(
|
||||||
QIcon(":assets/de.shorsh.discord-screenaudio.png"));
|
QIcon(":assets/de.shorsh.discord-screenaudio.png"));
|
||||||
QApplication::setApplicationVersion(DISCORD_SCEENAUDIO_VERSION_FULL);
|
QApplication::setApplicationVersion(DISCORD_SCEENAUDIO_VERSION_FULL);
|
||||||
|
QApplication::setDesktopFileName("de.shorsh.discord-screenaudio");
|
||||||
|
|
||||||
|
qSetMessagePattern("[%{category}] %{message}");
|
||||||
|
|
||||||
QCommandLineParser parser;
|
QCommandLineParser parser;
|
||||||
parser.setApplicationDescription(
|
parser.setApplicationDescription(
|
||||||
@@ -19,17 +27,29 @@ int main(int argc, char *argv[]) {
|
|||||||
QCommandLineOption virtmicOption("virtmic", "Start the Virtual Microphone",
|
QCommandLineOption virtmicOption("virtmic", "Start the Virtual Microphone",
|
||||||
"target");
|
"target");
|
||||||
parser.addOption(virtmicOption);
|
parser.addOption(virtmicOption);
|
||||||
#ifdef DEBUG
|
QCommandLineOption degubOption("remote-debugging",
|
||||||
parser.addOption(QCommandLineOption(
|
"Open Chromium Remote Debugging on port 9222");
|
||||||
"remote-debugging-port", "Chromium Remote Debugging Port", "port"));
|
parser.addOption(degubOption);
|
||||||
#endif
|
QCommandLineOption notifySendOption(
|
||||||
|
"notify-send", "Use notify-send instead of QT/KF5 notifications");
|
||||||
|
parser.addOption(notifySendOption);
|
||||||
|
|
||||||
parser.process(app);
|
parser.process(app);
|
||||||
|
|
||||||
if (parser.isSet(virtmicOption)) {
|
if (parser.isSet(virtmicOption)) {
|
||||||
Virtmic::start(parser.value(virtmicOption));
|
Virtmic::start(parser.value(virtmicOption));
|
||||||
}
|
}
|
||||||
|
|
||||||
MainWindow w;
|
qputenv("QTWEBENGINE_CHROMIUM_FLAGS",
|
||||||
|
"--enable-features=WebRTCPipeWireCapturer " +
|
||||||
|
qgetenv("QTWEBENGINE_CHROMIUM_FLAGS"));
|
||||||
|
|
||||||
|
if (parser.isSet(degubOption))
|
||||||
|
qputenv("QTWEBENGINE_CHROMIUM_FLAGS",
|
||||||
|
"--remote-debugging-port=9222 " +
|
||||||
|
qgetenv("QTWEBENGINE_CHROMIUM_FLAGS"));
|
||||||
|
|
||||||
|
MainWindow w(parser.isSet(notifySendOption));
|
||||||
w.show();
|
w.show();
|
||||||
|
|
||||||
return app.exec();
|
return app.exec();
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "virtmic.h"
|
#include "virtmic.h"
|
||||||
|
|
||||||
|
#ifdef KNOTIFICATIONS
|
||||||
|
#include <KNotification>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
@@ -11,13 +15,23 @@
|
|||||||
#include <QSpacerItem>
|
#include <QSpacerItem>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
#include <QWebEngineNotification>
|
||||||
|
#include <QWebEngineProfile>
|
||||||
#include <QWebEngineScript>
|
#include <QWebEngineScript>
|
||||||
#include <QWebEngineScriptCollection>
|
#include <QWebEngineScriptCollection>
|
||||||
#include <QWebEngineSettings>
|
#include <QWebEngineSettings>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
|
MainWindow *MainWindow::m_instance = nullptr;
|
||||||
|
|
||||||
|
MainWindow::MainWindow(bool useNotifySend, QWidget *parent)
|
||||||
|
: QMainWindow(parent) {
|
||||||
|
assert(MainWindow::m_instance == nullptr);
|
||||||
|
MainWindow::m_instance = this;
|
||||||
|
m_useNotifySend = useNotifySend;
|
||||||
|
setupSettings();
|
||||||
setupWebView();
|
setupWebView();
|
||||||
|
setupTrayIcon();
|
||||||
resize(1000, 700);
|
resize(1000, 700);
|
||||||
showMaximized();
|
showMaximized();
|
||||||
}
|
}
|
||||||
@@ -30,6 +44,38 @@ void MainWindow::setupWebView() {
|
|||||||
m_webView = new QWebEngineView(this);
|
m_webView = new QWebEngineView(this);
|
||||||
m_webView->setPage(page);
|
m_webView->setPage(page);
|
||||||
|
|
||||||
|
if (m_useKF5Notifications || m_useNotifySend)
|
||||||
|
QWebEngineProfile::defaultProfile()->setNotificationPresenter(
|
||||||
|
[&](std::unique_ptr<QWebEngineNotification> notificationInfo) {
|
||||||
|
if (m_useNotifySend) {
|
||||||
|
auto title = notificationInfo->title();
|
||||||
|
auto message = notificationInfo->message();
|
||||||
|
auto image_path =
|
||||||
|
QString("/tmp/discord-screenaudio-%1.png").arg(title);
|
||||||
|
notificationInfo->icon().save(image_path);
|
||||||
|
QProcess::execute("notify-send",
|
||||||
|
{"--icon", image_path, "--app-name",
|
||||||
|
"discord-screenaudio", title, message});
|
||||||
|
} else if (m_useKF5Notifications) {
|
||||||
|
#ifdef KNOTIFICATIONS
|
||||||
|
KNotification *notification =
|
||||||
|
new KNotification("discordNotification");
|
||||||
|
notification->setTitle(notificationInfo->title());
|
||||||
|
notification->setText(notificationInfo->message());
|
||||||
|
notification->setPixmap(
|
||||||
|
QPixmap::fromImage(notificationInfo->icon()));
|
||||||
|
notification->setDefaultAction("View");
|
||||||
|
connect(notification, &KNotification::defaultActivated,
|
||||||
|
[&, notificationInfo = std::move(notificationInfo)]() {
|
||||||
|
notificationInfo->click();
|
||||||
|
show();
|
||||||
|
activateWindow();
|
||||||
|
});
|
||||||
|
notification->sendEvent();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
setCentralWidget(m_webView);
|
setCentralWidget(m_webView);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,4 +90,72 @@ void MainWindow::fullScreenRequested(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::closeEvent(QCloseEvent *event) { QApplication::quit(); }
|
void MainWindow::setupTrayIcon() {
|
||||||
|
if (m_settings->value("trayIcon", false).toBool() == false ||
|
||||||
|
m_trayIcon != nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto aboutAction = new QAction(
|
||||||
|
"discord-screenaudio v" + QString(DISCORD_SCEENAUDIO_VERSION_FULL), this);
|
||||||
|
aboutAction->setIcon(QIcon(":assets/de.shorsh.discord-screenaudio.png"));
|
||||||
|
aboutAction->setEnabled(false);
|
||||||
|
|
||||||
|
auto exitAction = new QAction("Exit", this);
|
||||||
|
connect(exitAction, &QAction::triggered, []() { QApplication::quit(); });
|
||||||
|
|
||||||
|
m_trayIconMenu = new QMenu(this);
|
||||||
|
m_trayIconMenu->addAction(aboutAction);
|
||||||
|
m_trayIconMenu->addAction(exitAction);
|
||||||
|
|
||||||
|
m_trayIcon = new QSystemTrayIcon(this);
|
||||||
|
m_trayIcon->setContextMenu(m_trayIconMenu);
|
||||||
|
m_trayIcon->setIcon(QIcon(":assets/de.shorsh.discord-screenaudio.png"));
|
||||||
|
m_trayIcon->show();
|
||||||
|
|
||||||
|
connect(m_trayIcon, &QSystemTrayIcon::activated, [this](auto reason) {
|
||||||
|
if (reason == QSystemTrayIcon::Trigger) {
|
||||||
|
if (isVisible()) {
|
||||||
|
hide();
|
||||||
|
} else {
|
||||||
|
show();
|
||||||
|
activateWindow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::cleanTrayIcon() {
|
||||||
|
if (m_trayIcon == nullptr)
|
||||||
|
return;
|
||||||
|
m_trayIcon->hide();
|
||||||
|
m_trayIconMenu->deleteLater();
|
||||||
|
m_trayIcon->deleteLater();
|
||||||
|
m_trayIconMenu = nullptr;
|
||||||
|
m_trayIcon = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::setupSettings() {
|
||||||
|
m_settings = new QSettings("maltejur", "discord-screenaudio", this);
|
||||||
|
m_settings->beginGroup("settings");
|
||||||
|
m_settings->endGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
QSettings *MainWindow::settings() const { return m_settings; }
|
||||||
|
|
||||||
|
void MainWindow::setTrayIcon(bool enabled) {
|
||||||
|
m_settings->setValue("trayIcon", enabled);
|
||||||
|
if (enabled) {
|
||||||
|
setupTrayIcon();
|
||||||
|
} else {
|
||||||
|
cleanTrayIcon();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::closeEvent(QCloseEvent *event) {
|
||||||
|
if (m_settings->value("trayIcon", false).toBool()) {
|
||||||
|
hide();
|
||||||
|
} else
|
||||||
|
QApplication::quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
MainWindow *MainWindow::instance() { return m_instance; }
|
||||||
|
|||||||
@@ -3,8 +3,11 @@
|
|||||||
#include "discordpage.h"
|
#include "discordpage.h"
|
||||||
|
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
|
#include <QMenu>
|
||||||
#include <QScopedPointer>
|
#include <QScopedPointer>
|
||||||
|
#include <QSettings>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QSystemTrayIcon>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
#include <QWebEnginePage>
|
#include <QWebEnginePage>
|
||||||
#include <QWebEngineProfile>
|
#include <QWebEngineProfile>
|
||||||
@@ -14,15 +17,33 @@ class MainWindow : public QMainWindow {
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit MainWindow(QWidget *parent = nullptr);
|
explicit MainWindow(bool useNotifySend = false, QWidget *parent = nullptr);
|
||||||
|
static MainWindow *instance();
|
||||||
|
QSettings *settings() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setupWebView();
|
void setupWebView();
|
||||||
|
void setupTrayIcon();
|
||||||
|
void cleanTrayIcon();
|
||||||
|
void setupSettings();
|
||||||
QWebEngineView *m_webView;
|
QWebEngineView *m_webView;
|
||||||
QWebEngineProfile *prepareProfile();
|
QWebEngineProfile *prepareProfile();
|
||||||
DiscordPage *m_discordPage;
|
DiscordPage *m_discordPage;
|
||||||
void closeEvent(QCloseEvent *event) override;
|
void closeEvent(QCloseEvent *event) override;
|
||||||
|
QSystemTrayIcon *m_trayIcon = nullptr;
|
||||||
|
QMenu *m_trayIconMenu;
|
||||||
|
QSettings *m_settings;
|
||||||
bool m_wasMaximized;
|
bool m_wasMaximized;
|
||||||
|
static MainWindow *m_instance;
|
||||||
|
bool m_useNotifySend;
|
||||||
|
#ifdef KNOTIFICATIONS
|
||||||
|
bool m_useKF5Notifications = true;
|
||||||
|
#else
|
||||||
|
bool m_useKF5Notifications = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public Q_SLOTS:
|
||||||
|
void setTrayIcon(bool enabled);
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest);
|
void fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest);
|
||||||
|
|||||||
@@ -12,43 +12,77 @@
|
|||||||
StreamDialog::StreamDialog() : QWidget() {
|
StreamDialog::StreamDialog() : QWidget() {
|
||||||
setAttribute(Qt::WA_QuitOnClose, false);
|
setAttribute(Qt::WA_QuitOnClose, false);
|
||||||
|
|
||||||
|
{
|
||||||
auto layout = new QVBoxLayout(this);
|
auto layout = new QVBoxLayout(this);
|
||||||
layout->setSizeConstraint(QLayout::SetFixedSize);
|
layout->setSizeConstraint(QLayout::SetFixedSize);
|
||||||
|
|
||||||
|
m_videoGroupBox = new QGroupBox(this);
|
||||||
|
m_videoGroupBox->setTitle("Video");
|
||||||
|
m_videoGroupBox->setCheckable(true);
|
||||||
|
layout->addWidget(m_videoGroupBox);
|
||||||
|
|
||||||
|
{
|
||||||
|
auto videoLayout = new QVBoxLayout(this);
|
||||||
|
m_videoGroupBox->setLayout(videoLayout);
|
||||||
|
|
||||||
|
auto resolutionLabel = new QLabel(this);
|
||||||
|
resolutionLabel->setText("Resolution");
|
||||||
|
videoLayout->addWidget(resolutionLabel);
|
||||||
|
|
||||||
|
m_resolutionComboBox = new QComboBox(this);
|
||||||
|
m_resolutionComboBox->addItem("2160p", "3840x2160");
|
||||||
|
m_resolutionComboBox->addItem("1440p", "2560x1440");
|
||||||
|
m_resolutionComboBox->addItem("1080p", "1920x1080");
|
||||||
|
m_resolutionComboBox->addItem("720p", "1280x720");
|
||||||
|
m_resolutionComboBox->addItem("480p", "854x480");
|
||||||
|
m_resolutionComboBox->addItem("360p", "640x360");
|
||||||
|
m_resolutionComboBox->addItem("240p", "426x240");
|
||||||
|
m_resolutionComboBox->setCurrentText("720p");
|
||||||
|
videoLayout->addWidget(m_resolutionComboBox);
|
||||||
|
|
||||||
|
auto framerateLabel = new QLabel(this);
|
||||||
|
framerateLabel->setText("Framerate");
|
||||||
|
videoLayout->addWidget(framerateLabel);
|
||||||
|
|
||||||
|
m_framerateComboBox = new QComboBox(this);
|
||||||
|
m_framerateComboBox->addItem("144 FPS", 144);
|
||||||
|
m_framerateComboBox->addItem("60 FPS", 60);
|
||||||
|
m_framerateComboBox->addItem("30 FPS", 30);
|
||||||
|
m_framerateComboBox->addItem("15 FPS", 15);
|
||||||
|
m_framerateComboBox->addItem("5 FPS", 5);
|
||||||
|
m_framerateComboBox->setCurrentText("30 FPS");
|
||||||
|
videoLayout->addWidget(m_framerateComboBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_audioGroupBox = new QGroupBox(this);
|
||||||
|
m_audioGroupBox->setCheckable(true);
|
||||||
|
m_audioGroupBox->setTitle("Audio");
|
||||||
|
layout->addWidget(m_audioGroupBox);
|
||||||
|
|
||||||
|
{
|
||||||
|
auto audioLayout = new QVBoxLayout(this);
|
||||||
|
m_audioGroupBox->setLayout(audioLayout);
|
||||||
|
|
||||||
auto targetLabel = new QLabel(this);
|
auto targetLabel = new QLabel(this);
|
||||||
targetLabel->setText("Which app do you want to stream sound from?");
|
targetLabel->setText("Audio Source");
|
||||||
layout->addWidget(targetLabel);
|
audioLayout->addWidget(targetLabel);
|
||||||
|
|
||||||
|
{
|
||||||
|
auto targetLayout = new QHBoxLayout(this);
|
||||||
|
audioLayout->addLayout(targetLayout);
|
||||||
|
|
||||||
m_targetComboBox = new QComboBox(this);
|
m_targetComboBox = new QComboBox(this);
|
||||||
updateTargets();
|
updateTargets();
|
||||||
layout->addWidget(m_targetComboBox);
|
targetLayout->addWidget(m_targetComboBox);
|
||||||
|
|
||||||
auto qualityLabel = new QLabel(this);
|
auto refreshTargetsButton = new QPushButton(this);
|
||||||
qualityLabel->setText("Stream Quality");
|
refreshTargetsButton->setFixedSize(30, 30);
|
||||||
layout->addWidget(qualityLabel);
|
refreshTargetsButton->setIcon(QIcon::fromTheme("view-refresh"));
|
||||||
|
connect(refreshTargetsButton, &QPushButton::clicked, this,
|
||||||
auto qualityHBox = new QHBoxLayout(this);
|
&StreamDialog::updateTargets);
|
||||||
layout->addLayout(qualityHBox);
|
targetLayout->addWidget(refreshTargetsButton);
|
||||||
|
}
|
||||||
m_qualityResolutionComboBox = new QComboBox(this);
|
}
|
||||||
m_qualityResolutionComboBox->addItem("2160p", "3840x2160");
|
|
||||||
m_qualityResolutionComboBox->addItem("1440p", "2560x1440");
|
|
||||||
m_qualityResolutionComboBox->addItem("1080p", "1920x1080");
|
|
||||||
m_qualityResolutionComboBox->addItem("720p", "1280x720");
|
|
||||||
m_qualityResolutionComboBox->addItem("480p", "854x480");
|
|
||||||
m_qualityResolutionComboBox->addItem("360p", "640x360");
|
|
||||||
m_qualityResolutionComboBox->addItem("240p", "426x240");
|
|
||||||
m_qualityResolutionComboBox->setCurrentText("720p");
|
|
||||||
qualityHBox->addWidget(m_qualityResolutionComboBox);
|
|
||||||
|
|
||||||
m_qualityFPSComboBox = new QComboBox(this);
|
|
||||||
m_qualityFPSComboBox->addItem("144 FPS", 144);
|
|
||||||
m_qualityFPSComboBox->addItem("60 FPS", 60);
|
|
||||||
m_qualityFPSComboBox->addItem("30 FPS", 30);
|
|
||||||
m_qualityFPSComboBox->addItem("15 FPS", 15);
|
|
||||||
m_qualityFPSComboBox->addItem("5 FPS", 5);
|
|
||||||
m_qualityFPSComboBox->setCurrentText("30 FPS");
|
|
||||||
qualityHBox->addWidget(m_qualityFPSComboBox);
|
|
||||||
|
|
||||||
auto button = new QPushButton(this);
|
auto button = new QPushButton(this);
|
||||||
button->setText("Start Stream");
|
button->setText("Start Stream");
|
||||||
@@ -56,23 +90,32 @@ StreamDialog::StreamDialog() : QWidget() {
|
|||||||
layout->addWidget(button, Qt::AlignRight | Qt::AlignBottom);
|
layout->addWidget(button, Qt::AlignRight | Qt::AlignBottom);
|
||||||
|
|
||||||
setLayout(layout);
|
setLayout(layout);
|
||||||
|
}
|
||||||
|
|
||||||
setWindowTitle("discord-screenaudio Stream Dialog");
|
setWindowTitle("discord-screenaudio Stream Dialog");
|
||||||
}
|
}
|
||||||
|
|
||||||
void StreamDialog::startStream() {
|
void StreamDialog::startStream() {
|
||||||
auto resolution =
|
auto resolution = m_resolutionComboBox->currentData().toString().split('x');
|
||||||
m_qualityResolutionComboBox->currentData().toString().split('x');
|
emit requestedStreamStart(m_videoGroupBox->isChecked(),
|
||||||
emit requestedStreamStart(m_targetComboBox->currentText(),
|
m_audioGroupBox->isChecked(),
|
||||||
resolution[0].toUInt(), resolution[1].toUInt(),
|
resolution[0].toUInt(), resolution[1].toUInt(),
|
||||||
m_qualityFPSComboBox->currentData().toUInt());
|
m_framerateComboBox->currentData().toUInt(),
|
||||||
|
m_targetComboBox->currentText());
|
||||||
setHidden(true);
|
setHidden(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void StreamDialog::updateTargets() {
|
void StreamDialog::updateTargets() {
|
||||||
|
auto lastTarget = m_targetComboBox->currentText();
|
||||||
|
|
||||||
m_targetComboBox->clear();
|
m_targetComboBox->clear();
|
||||||
m_targetComboBox->addItem("None");
|
m_targetComboBox->addItem("[All Desktop Audio]");
|
||||||
for (auto target : Virtmic::getTargets()) {
|
for (auto target : Virtmic::getTargets()) {
|
||||||
m_targetComboBox->addItem(target);
|
m_targetComboBox->addItem(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_targetComboBox->findText(lastTarget) != -1)
|
||||||
|
m_targetComboBox->setCurrentText(lastTarget);
|
||||||
|
else
|
||||||
|
m_targetComboBox->setCurrentText("[All Desktop Audio]");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
|
#include <QGroupBox>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
class StreamDialog : public QWidget {
|
class StreamDialog : public QWidget {
|
||||||
@@ -12,12 +13,14 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
QComboBox *m_targetComboBox;
|
QComboBox *m_targetComboBox;
|
||||||
QComboBox *m_qualityResolutionComboBox;
|
QComboBox *m_resolutionComboBox;
|
||||||
QComboBox *m_qualityFPSComboBox;
|
QComboBox *m_framerateComboBox;
|
||||||
|
QGroupBox *m_videoGroupBox;
|
||||||
|
QGroupBox *m_audioGroupBox;
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void requestedStreamStart(QString target, uint width, uint height,
|
void requestedStreamStart(bool video, bool audio, uint width, uint height,
|
||||||
uint frameRate);
|
uint frameRate, QString target);
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
void updateTargets();
|
void updateTargets();
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
#include "virtmic.h"
|
#include "virtmic.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
#include <rohrkabel/loop/main.hpp>
|
#include <rohrkabel/loop/main.hpp>
|
||||||
#include <rohrkabel/registry/registry.hpp>
|
#include <rohrkabel/registry/registry.hpp>
|
||||||
|
|
||||||
namespace Virtmic {
|
namespace Virtmic {
|
||||||
|
|
||||||
|
const QStringList EXCLUDE_TARGETS{"Chromium input", "discord-screenaudio"};
|
||||||
|
|
||||||
QVector<QString> getTargets() {
|
QVector<QString> getTargets() {
|
||||||
auto main_loop = pipewire::main_loop();
|
auto main_loop = pipewire::main_loop();
|
||||||
auto context = pipewire::context(main_loop);
|
auto context = pipewire::context(main_loop);
|
||||||
@@ -19,10 +22,16 @@ QVector<QString> getTargets() {
|
|||||||
if (global.type == pipewire::node::type) {
|
if (global.type == pipewire::node::type) {
|
||||||
auto node = reg.bind<pipewire::node>(global.id);
|
auto node = reg.bind<pipewire::node>(global.id);
|
||||||
auto info = node.info();
|
auto info = node.info();
|
||||||
|
QString name;
|
||||||
|
if (info.props.count("application.name") &&
|
||||||
|
info.props["application.name"] != "")
|
||||||
|
name = QString::fromStdString(info.props["application.name"]);
|
||||||
|
else
|
||||||
|
name = QString::fromStdString(
|
||||||
|
info.props["application.process.binary"]);
|
||||||
|
|
||||||
if (info.props.count("node.name")) {
|
if (name != "" && !EXCLUDE_TARGETS.contains(name) &&
|
||||||
auto name = QString::fromStdString(info.props["node.name"]);
|
!targets.contains(name)) {
|
||||||
if (!targets.contains(name))
|
|
||||||
targets.append(name);
|
targets.append(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,20 +73,28 @@ void start(QString _target) {
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
auto &parent = nodes.at(parent_id);
|
auto &parent = nodes.at(parent_id);
|
||||||
|
std::string name;
|
||||||
|
if (parent.props.count("application.name") &&
|
||||||
|
parent.props["application.name"] != "")
|
||||||
|
name = parent.props["application.name"];
|
||||||
|
else
|
||||||
|
name = parent.props["application.process.binary"];
|
||||||
|
|
||||||
if (parent.props["node.name"].find(target) != std::string::npos) {
|
if (name == target ||
|
||||||
std::cout << "[virtmic] "
|
(target == "[All Desktop Audio]" &&
|
||||||
<< "Link : " << target << ":" << port_id << " -> ";
|
!EXCLUDE_TARGETS.contains(QString::fromStdString(name)))) {
|
||||||
|
auto fl = port.info().props["audio.channel"] == "FL";
|
||||||
if (port.info().props["audio.channel"] == "FL") {
|
links.emplace(
|
||||||
links.emplace(port_id, core.create<pipewire::link_factory>(
|
port_id,
|
||||||
{virt_fl->info().id, port_id}));
|
core.create<pipewire::link_factory>(
|
||||||
std::cout << "[virtmic] " << virt_fl->info().id << std::endl;
|
{fl ? virt_fl->info().id : virt_fr->info().id, port_id}));
|
||||||
} else {
|
qDebug(virtmicLog) << QString("Link: %1:%2 -> %3")
|
||||||
links.emplace(port_id, core.create<pipewire::link_factory>(
|
.arg(QString::fromStdString(name))
|
||||||
{virt_fr->info().id, port_id}));
|
.arg(port_id)
|
||||||
std::cout << "[virtmic] " << virt_fr->info().id << std::endl;
|
.arg(fl ? virt_fl->info().id
|
||||||
}
|
: virt_fr->info().id)
|
||||||
|
.toUtf8()
|
||||||
|
.data();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -93,7 +110,7 @@ void start(QString _target) {
|
|||||||
pipewire::node::type, pipewire::node::version,
|
pipewire::node::type, pipewire::node::version,
|
||||||
pipewire::update_strategy::none);
|
pipewire::update_strategy::none);
|
||||||
|
|
||||||
if (target == "None") {
|
if (target == "[None]") {
|
||||||
while (true) {
|
while (true) {
|
||||||
main_loop.run();
|
main_loop.run();
|
||||||
}
|
}
|
||||||
@@ -105,9 +122,19 @@ void start(QString _target) {
|
|||||||
[&](const pipewire::global &global) {
|
[&](const pipewire::global &global) {
|
||||||
if (global.type == pipewire::node::type) {
|
if (global.type == pipewire::node::type) {
|
||||||
auto node = reg.bind<pipewire::node>(global.id);
|
auto node = reg.bind<pipewire::node>(global.id);
|
||||||
std::cout << "[virtmic] "
|
auto info = node.info();
|
||||||
<< "Added : " << node.info().props["node.name"]
|
std::string name;
|
||||||
<< std::endl;
|
if (info.props.count("application.name") &&
|
||||||
|
info.props["application.name"] != "")
|
||||||
|
name = info.props["application.name"];
|
||||||
|
else if (info.props.count("application.process.binary")) {
|
||||||
|
name = info.props["application.process.binary"];
|
||||||
|
} else
|
||||||
|
return;
|
||||||
|
qDebug(virtmicLog) << QString("Added: %1")
|
||||||
|
.arg(QString::fromStdString(name))
|
||||||
|
.toUtf8()
|
||||||
|
.data();
|
||||||
|
|
||||||
if (!nodes.count(global.id)) {
|
if (!nodes.count(global.id)) {
|
||||||
nodes.emplace(global.id, node.info());
|
nodes.emplace(global.id, node.info());
|
||||||
@@ -141,8 +168,16 @@ void start(QString _target) {
|
|||||||
[&](const std::uint32_t id) {
|
[&](const std::uint32_t id) {
|
||||||
if (nodes.count(id)) {
|
if (nodes.count(id)) {
|
||||||
auto info = nodes.at(id);
|
auto info = nodes.at(id);
|
||||||
std::cout << "[virtmic] "
|
std::string name;
|
||||||
<< "Removed: " << info.props["node.name"] << std::endl;
|
if (info.props.count("application.name") &&
|
||||||
|
info.props["application.name"] != "")
|
||||||
|
name = info.props["application.name"];
|
||||||
|
else
|
||||||
|
name = info.props["application.process.binary"];
|
||||||
|
qDebug(virtmicLog) << QString("Removed: %1")
|
||||||
|
.arg(QString::fromStdString(name))
|
||||||
|
.toUtf8()
|
||||||
|
.data();
|
||||||
nodes.erase(id);
|
nodes.erase(id);
|
||||||
}
|
}
|
||||||
if (ports.count(id)) {
|
if (ports.count(id)) {
|
||||||
|
|||||||
@@ -138,11 +138,12 @@ function(determine_version source_dir var_prefix)
|
|||||||
# | exact tag | 0.9.0 |
|
# | exact tag | 0.9.0 |
|
||||||
# | exact tag, dirty | 0.9.0+git20180604 |
|
# | exact tag, dirty | 0.9.0+git20180604 |
|
||||||
# | after tag | 0.9.0+git20180604.1.085039f |
|
# | after tag | 0.9.0+git20180604.1.085039f |
|
||||||
# | no tag, version.txt | 0.9.0+git20180604.2ee02af |
|
# | no tag, version.txt | 0.9.0 |
|
||||||
# | no tag, no version.txt| 0.0.0+git20180604.2ee02af |
|
# | no tag, no version.txt| 0.0.0+git20180604.2ee02af |
|
||||||
string(TIMESTAMP date "%Y%m%d" UTC)
|
string(TIMESTAMP date "%Y%m%d" UTC)
|
||||||
set(version_suffix "")
|
set(version_suffix "")
|
||||||
if (NOT ${local_prefix}_VERSION_NCOMMITS EQUAL 0)
|
if (has_version_txt)
|
||||||
|
elseif (NOT ${local_prefix}_VERSION_NCOMMITS EQUAL 0)
|
||||||
# 0.9.0+git20150604.4.289818b
|
# 0.9.0+git20150604.4.289818b
|
||||||
string(APPEND version_suffix "+git${date}.${${local_prefix}_VERSION_NCOMMITS}.${${local_prefix}_VERSION_GIT_REV}")
|
string(APPEND version_suffix "+git${date}.${${local_prefix}_VERSION_NCOMMITS}.${${local_prefix}_VERSION_GIT_REV}")
|
||||||
elseif ((NOT has_release_tag) AND ((NOT has_version_txt) OR ("${base_version}" STREQUAL "0.0.0") OR (NOT "${revision}" STREQUAL "")))
|
elseif ((NOT has_release_tag) AND ((NOT has_version_txt) OR ("${base_version}" STREQUAL "0.0.0") OR (NOT "${revision}" STREQUAL "")))
|
||||||
|
|||||||
Reference in New Issue
Block a user