Compare commits
	
		
			1 Commits
		
	
	
		
			autogain
			...
			testing_ha
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | bdabf68c9b | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +1,2 @@ | |||||||
| /build | /build | ||||||
| .vscode | .vscode | ||||||
| /submodules/arrpc |  | ||||||
|   | |||||||
| @@ -13,27 +13,24 @@ 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 COMPONENTS Widgets) | find_package(Qt5 CONFIG REQUIRED COMPONENTS  | ||||||
| if (Qt5_FOUND) |   Widgets | ||||||
|   find_package(Qt5 CONFIG REQUIRED COMPONENTS Widgets WebEngineWidgets) |   WebEngineWidgets | ||||||
|  | ) | ||||||
|  |  | ||||||
|   find_package(KF5Notifications) | find_package(KF5Notifications) | ||||||
|   if(KF5Notifications_FOUND) | if(KF5Notifications_FOUND) | ||||||
|     add_definitions( -DKNOTIFICATIONS ) |   add_definitions( -DKNOTIFICATIONS ) | ||||||
|   endif() | endif() | ||||||
|  |  | ||||||
|   find_package(KF5XmlGui) | find_package(KF5XmlGui) | ||||||
|   if(KF5XmlGui_FOUND) | if(KF5XmlGui_FOUND) | ||||||
|     add_definitions( -DKXMLGUI ) |   add_definitions( -DKXMLGUI ) | ||||||
|   endif() | endif() | ||||||
|  |  | ||||||
|   find_package(KF5GlobalAccel) | find_package(KF5GlobalAccel) | ||||||
|   if(KF5GlobalAccel_FOUND) | if(KF5GlobalAccel_FOUND) | ||||||
|     add_definitions( -DKGLOBALACCEL ) |   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() | endif() | ||||||
|  |  | ||||||
| set(discord-screenaudio_SRC | set(discord-screenaudio_SRC | ||||||
| @@ -43,9 +40,6 @@ set(discord-screenaudio_SRC | |||||||
|   src/discordpage.cpp |   src/discordpage.cpp | ||||||
|   src/streamdialog.cpp |   src/streamdialog.cpp | ||||||
|   src/log.cpp |   src/log.cpp | ||||||
|   src/userscript.cpp |  | ||||||
|   src/centralwidget.cpp |  | ||||||
|   src/localserver.cpp |  | ||||||
|   resources.qrc |   resources.qrc | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -72,7 +66,7 @@ add_subdirectory(submodules/rohrkabel) | |||||||
|  |  | ||||||
| add_executable(discord-screenaudio ${discord-screenaudio_SRC}) | add_executable(discord-screenaudio ${discord-screenaudio_SRC}) | ||||||
|  |  | ||||||
| target_link_libraries(discord-screenaudio Qt::Widgets Qt::WebEngineWidgets rohrkabel) | target_link_libraries(discord-screenaudio Qt5::Widgets Qt5::WebEngineWidgets rohrkabel) | ||||||
|  |  | ||||||
| if(KF5Notifications_FOUND) | if(KF5Notifications_FOUND) | ||||||
|   target_link_libraries(discord-screenaudio KF5::Notifications) |   target_link_libraries(discord-screenaudio KF5::Notifications) | ||||||
|   | |||||||
							
								
								
									
										32
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								README.md
									
									
									
									
									
								
							| @@ -9,9 +9,9 @@ of [@edisionnano](https://github.com/edisionnano) and the | |||||||
|  |  | ||||||
| Unlike a lot of other solutions, the audio here is directly fed into the | Unlike a lot of other solutions, the audio here is directly fed into the | ||||||
| screenshare and not passed to the user microphone | screenshare and not passed to the user microphone | ||||||
| ([see explanation](#how-does-this-work)). | ([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 | ||||||
| @@ -50,8 +50,6 @@ You have multiple options: | |||||||
| ### 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) | ||||||
| @@ -59,12 +57,9 @@ You have multiple options: | |||||||
| - _Kf5Notifications (optional, for better notifications)_ | - _Kf5Notifications (optional, for better notifications)_ | ||||||
| - _KXMLGui and KGlobalAccel (optional, for keybinds)_ | - _KXMLGui and KGlobalAccel (optional, for keybinds)_ | ||||||
|  |  | ||||||
| With apt: | On Debian: | ||||||
| `apt install -y build-essential cmake qtbase5-dev qtwebengine5-dev libkf5notifications-dev libkf5xmlgui-dev libkf5globalaccel-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` | ||||||
|  |  | ||||||
| With dnf: |  | ||||||
| `dnf install @development-tools cmake qt5-qtbase-devel qt5-qtwebengine-devel kf5-knotifications-devel kf5-kxmlgui-devel kf5-kglobalaccel-devel pkgconfig pipewire-devel git` |  | ||||||
|  |  | ||||||
| ### Building | ### Building | ||||||
|  |  | ||||||
| First, clone the repository: | First, clone the repository: | ||||||
| @@ -87,9 +82,7 @@ And then to optionally install it, run: | |||||||
| sudo cmake --install build | sudo cmake --install build | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| ## FAQ | ## How it works | ||||||
|  |  | ||||||
| ### How does this work? |  | ||||||
|  |  | ||||||
| This whole project is based on | This whole project is based on | ||||||
| [this](https://github.com/edisionnano/Screenshare-with-audio-on-Discord-with-Linux) | [this](https://github.com/edisionnano/Screenshare-with-audio-on-Discord-with-Linux) | ||||||
| @@ -98,23 +91,6 @@ Discord. Basically: a virtual microphone is created which captures the | |||||||
| application audio, and this microphone is then fed to the Discord stream by | application audio, and this microphone is then fed to the Discord stream by | ||||||
| intercepting a API call of Discord. | intercepting a API call of Discord. | ||||||
|  |  | ||||||
| ### Drag and drop doesn't work in the Flatpak |  | ||||||
|  |  | ||||||
| This is due to sandboxing limitations of Flatpak. The main Discord Flatpak has |  | ||||||
| the same problem. If you still want to use drag and drop, you can disable most |  | ||||||
| of Flatpak's sandboxing by installing |  | ||||||
| [Flatseal](https://flathub.org/apps/details/com.github.tchx84.Flatseal) and |  | ||||||
| allowing access to "All system files" under the "Filesystem" section. |  | ||||||
|  |  | ||||||
| ### Is there any way to add custom CSS / a theme? |  | ||||||
|  |  | ||||||
| Yes, you can add all your styles into |  | ||||||
| `~/.config/discord-screenaudio/userstyles.css` (or |  | ||||||
| `~/.var/app/de.shorsh.discord-screenaudio/config/discord-screenaudio/userstyles.css` |  | ||||||
| if you are using the Flatpak). But please note that due to QtWebEngine |  | ||||||
| limitations concerning content security policies, you can't use any external |  | ||||||
| files (like `@import` or `url()`). |  | ||||||
|  |  | ||||||
| ## License | ## License | ||||||
|  |  | ||||||
| Copyright (C) 2022 Malte Jürgens | Copyright (C) 2022 Malte Jürgens | ||||||
|   | |||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -1 +0,0 @@ | |||||||
| setTimeout((i=>{let t,a,s,e={};new WebSocket("ws://127.0.0.1:1337").onmessage=async i=>{if(msg=JSON.parse(i.data),console.log(msg),!t){const i=window.webpackChunkdiscord_app.push([[Symbol()],{},i=>i]),e=i.c;window.webpackChunkdiscord_app.pop();for(const i in e){let a=e[i].exports;if(a=a&&(a.Z??a.ZP),a&&a.register&&a.wait){t=a;break}}const n=i.m;for(const t in n)if(n[t].toString().includes("getAssetImage: size must === [number, number] for Twitch")){const s=i(t),e=Object.values(s).find((i=>"function"==typeof i&&i.toString().includes("apply(")));a=async(i,t)=>(await e(i,[t,void 0]))[0];break}for(const t in n)if(n[t].toString().includes("e.application={")){const a=i(t),e=Object.values(a).find((i=>"function"==typeof i&&i.toString().includes("e.application={")));s=async i=>{let t={};return await e(t,i),t.application};break}}if(msg.activity?.assets?.large_image&&(msg.activity.assets.large_image=await a(msg.activity.application_id,msg.activity.assets.large_image)),msg.activity?.assets?.small_image&&(msg.activity.assets.small_image=await a(msg.activity.application_id,msg.activity.assets.small_image)),msg.activity){const i=msg.activity.application_id;e[i]||(e[i]=await s(i));const t=e[i];msg.activity.name||(msg.activity.name=t.name)}t.dispatch({type:"LOCAL_ACTIVITY_UPDATE",...msg})}}),1e4); |  | ||||||
| @@ -4,7 +4,6 @@ | |||||||
|   <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> | ||||||
|   | |||||||
| @@ -1,14 +1,14 @@ | |||||||
|  | // From v0.4 | ||||||
|  |  | ||||||
| navigator.mediaDevices.chromiumGetDisplayMedia = | navigator.mediaDevices.chromiumGetDisplayMedia = | ||||||
|   navigator.mediaDevices.getDisplayMedia; |   navigator.mediaDevices.getDisplayMedia; | ||||||
| navigator.mediaDevices.chromiumGetUserMedia = |  | ||||||
|   navigator.mediaDevices.getUserMedia; |  | ||||||
|  |  | ||||||
| function sleep(ms) { | function sleep(ms) { | ||||||
|   return new Promise((resolve) => setTimeout(resolve, ms)); |   return new Promise((resolve) => setTimeout(resolve, ms)); | ||||||
| } | } | ||||||
|  |  | ||||||
| const getAudioDevice = async (nameOfAudioDevice) => { | const getAudioDevice = async (nameOfAudioDevice) => { | ||||||
|   await navigator.mediaDevices.chromiumGetUserMedia({ |   await navigator.mediaDevices.getUserMedia({ | ||||||
|     audio: true, |     audio: true, | ||||||
|   }); |   }); | ||||||
|   let audioDevice; |   let audioDevice; | ||||||
| @@ -16,26 +16,16 @@ const getAudioDevice = async (nameOfAudioDevice) => { | |||||||
|     let devices = await navigator.mediaDevices.enumerateDevices(); |     let devices = await navigator.mediaDevices.enumerateDevices(); | ||||||
|     audioDevice = devices.find(({ label }) => label === nameOfAudioDevice); |     audioDevice = devices.find(({ label }) => label === nameOfAudioDevice); | ||||||
|     if (!audioDevice) |     if (!audioDevice) | ||||||
|       userscript.log( |       console.log( | ||||||
|         `Did not find '${nameOfAudioDevice}', trying again in 100ms` |         `dsa: Did not find '${nameOfAudioDevice}', trying again in 100ms` | ||||||
|       ); |       ); | ||||||
|     await sleep(100); |     await sleep(100); | ||||||
|   } |   } | ||||||
|   userscript.log(`Found '${nameOfAudioDevice}'`); |   console.log(`dsa: Found '${nameOfAudioDevice}'`); | ||||||
|   return audioDevice; |   return audioDevice; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| function setGetUserMedia() { | function setGetDisplayMedia(overrideArgs = undefined) { | ||||||
|   const getUserMedia = async (constraints) => { |  | ||||||
|     return await navigator.mediaDevices.chromiumGetUserMedia({ |  | ||||||
|       video: constraints?.video || false, |  | ||||||
|       audio: { ...constraints?.audio, autoGainControl }, |  | ||||||
|     }); |  | ||||||
|   }; |  | ||||||
|   navigator.mediaDevices.getUserMedia = getUserMedia; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function setGetDisplayMedia(video = true, overrideArgs = undefined) { |  | ||||||
|   const getDisplayMedia = async (...args) => { |   const getDisplayMedia = async (...args) => { | ||||||
|     var id; |     var id; | ||||||
|     try { |     try { | ||||||
| @@ -46,27 +36,26 @@ function setGetDisplayMedia(video = true, overrideArgs = undefined) { | |||||||
|     } catch (error) { |     } catch (error) { | ||||||
|       id = "default"; |       id = "default"; | ||||||
|     } |     } | ||||||
|     let captureSystemAudioStream = |     let captureSystemAudioStream = await navigator.mediaDevices.getUserMedia({ | ||||||
|       await navigator.mediaDevices.chromiumGetUserMedia({ |       audio: { | ||||||
|         audio: { |         // We add our audio constraints here, to get a list of supported constraints use navigator.mediaDevices.getSupportedConstraints(); | ||||||
|           // We add our audio constraints here, to get a list of supported constraints use navigator.mediaDevices.getSupportedConstraints(); |         // We must capture a microphone, we use default since its the only deviceId that is the same for every Chromium user | ||||||
|           // We must capture a microphone, we use default since its the only deviceId that is the same for every Chromium user |         deviceId: { | ||||||
|           deviceId: { |           exact: id, | ||||||
|             exact: id, |  | ||||||
|           }, |  | ||||||
|           // We want auto gain control, noise cancellation and noise suppression disabled so that our stream won't sound bad |  | ||||||
|           autoGainControl: false, |  | ||||||
|           echoCancellation: false, |  | ||||||
|           noiseSuppression: false, |  | ||||||
|           // By default Chromium sets channel count for audio devices to 1, we want it to be stereo in case we find a way for Discord to accept stereo screenshare too |  | ||||||
|           channelCount: 2, |  | ||||||
|           // You can set more audio constraints here, bellow are some examples |  | ||||||
|           //latency: 0, |  | ||||||
|           //sampleRate: 48000, |  | ||||||
|           //sampleSize: 16, |  | ||||||
|           //volume: 1.0 |  | ||||||
|         }, |         }, | ||||||
|       }); |         // We want auto gain control, noise cancellation and noise suppression disabled so that our stream won't sound bad | ||||||
|  |         autoGainControl: false, | ||||||
|  |         echoCancellation: false, | ||||||
|  |         noiseSuppression: false, | ||||||
|  |         // By default Chromium sets channel count for audio devices to 1, we want it to be stereo in case we find a way for Discord to accept stereo screenshare too | ||||||
|  |         channelCount: 2, | ||||||
|  |         // You can set more audio constraints here, bellow are some examples | ||||||
|  |         //latency: 0, | ||||||
|  |         //sampleRate: 48000, | ||||||
|  |         //sampleSize: 16, | ||||||
|  |         //volume: 1.0 | ||||||
|  |       }, | ||||||
|  |     }); | ||||||
|     let [track] = captureSystemAudioStream.getAudioTracks(); |     let [track] = captureSystemAudioStream.getAudioTracks(); | ||||||
|     const gdm = await navigator.mediaDevices.chromiumGetDisplayMedia( |     const gdm = await navigator.mediaDevices.chromiumGetDisplayMedia( | ||||||
|       ...(overrideArgs |       ...(overrideArgs | ||||||
| @@ -74,342 +63,144 @@ function setGetDisplayMedia(video = true, 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; | ||||||
| } | } | ||||||
|  |  | ||||||
| setGetDisplayMedia(); | setGetDisplayMedia(); | ||||||
| setGetUserMedia(); |  | ||||||
|  |  | ||||||
| let userscript; |  | ||||||
| let muteBtn; |  | ||||||
| let deafenBtn; |  | ||||||
| let streamStartBtn; |  | ||||||
| let streamStartBtnInitialDisplay; |  | ||||||
| let streamStartBtnClone; |  | ||||||
| let resolutionString; |  | ||||||
| let autoGainControl = true; |  | ||||||
| const clonedElements = []; | const clonedElements = []; | ||||||
| const hiddenElements = []; | const hiddenElements = []; | ||||||
| let wasStreamActive = false; | let wasStreamActive = false; | ||||||
|  |  | ||||||
| function createButton(text, onClick) { | setInterval(() => { | ||||||
|   const button = document.createElement("button"); |   const streamActive = | ||||||
|   button.style.marginBottom = "20px"; |     document.getElementsByClassName("panel-2ZFCRb activityPanel-9icbyU") | ||||||
|   button.classList = |       .length > 0; | ||||||
|     "button-ejjZWC lookFilled-1H2Jvj colorBrand-2M3O3N sizeSmall-3R2P2p grow-2T4nbg"; |  | ||||||
|   button.innerText = text; |  | ||||||
|   button.addEventListener("click", onClick); |  | ||||||
|   return button; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function createSwitch(text, enabled, onClick) { |   if (!streamActive && wasStreamActive) | ||||||
|   const container = document.createElement("div"); |     console.log("!discord-screenaudio-stream-stopped"); | ||||||
|   container.style.marginBottom = "20px"; |   wasStreamActive = streamActive; | ||||||
|   container.className = "labelRow-NnoUIp"; |  | ||||||
|  |  | ||||||
|   const label = document.createElement("label"); |   if (streamActive) { | ||||||
|   label.innerText = text; |     clonedElements.forEach((el) => { | ||||||
|   label.className = "title-2yADjX"; |       el.remove(); | ||||||
|   container.appendChild(label); |     }); | ||||||
|  |     clonedElements.length = 0; | ||||||
|  |  | ||||||
|   const svg = document.createElement("div"); |     hiddenElements.forEach((el) => { | ||||||
|   container.appendChild(svg); |       el.style.display = "block"; | ||||||
|  |     }); | ||||||
|  |     hiddenElements.length = 0; | ||||||
|  |   } else { | ||||||
|  |     for (const el of [ | ||||||
|  |       document.getElementsByClassName("actionButtons-2vEOUh")?.[0]?.children[1], | ||||||
|  |       document.querySelector( | ||||||
|  |         ".wrapper-3t3Yqv > div > div > div > div > .controlButton-2PMNom" | ||||||
|  |       ), | ||||||
|  |     ]) { | ||||||
|  |       if (!el) continue; | ||||||
|  |       if (el.classList.contains("discord-screenaudio-cloned")) continue; | ||||||
|  |       el.classList.add("discord-screenaudio-cloned"); | ||||||
|  |       elClone = el.cloneNode(true); | ||||||
|  |       elClone.title = "Share Your Screen with Audio"; | ||||||
|  |       elClone.addEventListener("click", () => { | ||||||
|  |         console.log("!discord-screenaudio-start-stream"); | ||||||
|  |       }); | ||||||
|  |  | ||||||
|   function setSvgDisabled() { |       const initialDisplay = el.style.display; | ||||||
|     svg.innerHTML = `<div class="container-1QtPKm default-colors" style="opacity: 1; 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_84" type="checkbox" class="input-125oad" tabindex="0"></div>`; |  | ||||||
|  |       window.discordScreenaudioStartStream = (width, height, frameRate) => { | ||||||
|  |         window.discordScreenaudioResolutionString = `${height}p ${frameRate}FPS`; | ||||||
|  |         setGetDisplayMedia({ | ||||||
|  |           audio: true, | ||||||
|  |           video: { width, height, frameRate }, | ||||||
|  |         }); | ||||||
|  |         el.click(); | ||||||
|  |         el.style.display = initialDisplay; | ||||||
|  |         elClone.remove(); | ||||||
|  |       }; | ||||||
|  |  | ||||||
|  |       el.style.display = "none"; | ||||||
|  |       el.parentNode.insertBefore(elClone, el); | ||||||
|  |  | ||||||
|  |       clonedElements.push(elClone); | ||||||
|  |       hiddenElements.push(el); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   function setSvgEnabled() { |   // Add about text in settings | ||||||
|     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>`; |   if ( | ||||||
|  |     document.getElementsByClassName("dirscordScreenaudioAboutText").length == 0 | ||||||
|  |   ) { | ||||||
|  |     for (const el of document.getElementsByClassName("info-3pQQBb")) { | ||||||
|  |       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.style.fontSize = "12px"; | ||||||
|  |       aboutEl.style.color = "var(--text-muted)"; | ||||||
|  |       aboutEl.style.textTransform = "none"; | ||||||
|  |       aboutEl.classList.add("dirscordScreenaudioAboutText"); | ||||||
|  |       aboutEl.style.cursor = "pointer"; | ||||||
|  |       el.appendChild(aboutEl); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   function updateSvg() { |   // Remove stream settings if stream is active | ||||||
|     if (enabled) setSvgEnabled(); |   document.getElementById("manage-streams-change-windows")?.remove(); | ||||||
|     else setSvgDisabled(); |   document.querySelector(`[aria-label="Stream Settings"]`)?.remove(); | ||||||
|  |  | ||||||
|  |   // Add event listener for keybind tab | ||||||
|  |   if ( | ||||||
|  |     document | ||||||
|  |       .getElementById("keybinds-tab") | ||||||
|  |       ?.getElementsByClassName( | ||||||
|  |         "container-3jbRo5 info-1hMolH fontSize16-3zr6Io browserNotice-1u-Y5o" | ||||||
|  |       ).length | ||||||
|  |   ) { | ||||||
|  |     const el = document | ||||||
|  |       .getElementById("keybinds-tab") | ||||||
|  |       .getElementsByClassName("children-1xdcWE")[0]; | ||||||
|  |     const div = document.createElement("div"); | ||||||
|  |     div.style.marginBottom = "50px"; | ||||||
|  |     const button = document.createElement("button"); | ||||||
|  |     button.classList = | ||||||
|  |       "button-f2h6uQ lookFilled-yCfaCM colorBrand-I6CyqQ sizeSmall-wU2dO- grow-2sR_-F"; | ||||||
|  |     button.innerText = "Edit Global Keybinds"; | ||||||
|  |     button.addEventListener("click", () => { | ||||||
|  |       console.log("!discord-screenaudio-keybinds"); | ||||||
|  |     }); | ||||||
|  |     div.appendChild(button); | ||||||
|  |     el.innerHTML = ""; | ||||||
|  |     el.appendChild(div); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   container.addEventListener("click", () => { |   const muteBtn = document.getElementsByClassName( | ||||||
|     enabled = !enabled; |     "button-12Fmur enabled-9OeuTA button-f2h6uQ lookBlank-21BCro colorBrand-I6CyqQ grow-2sR_-F" | ||||||
|     updateSvg(); |   )[0]; | ||||||
|     onClick(enabled); |   window.discordScreenaudioToggleMute = () => muteBtn.click(); | ||||||
|   }); |   const deafenBtn = document.getElementsByClassName( | ||||||
|   updateSvg(); |     "button-12Fmur enabled-9OeuTA button-f2h6uQ lookBlank-21BCro colorBrand-I6CyqQ grow-2sR_-F" | ||||||
|  |   )[1]; | ||||||
|  |   window.discordScreenaudioToggleDeafen = () => deafenBtn.click(); | ||||||
|  |  | ||||||
|   return container; |   if (window.discordScreenaudioResolutionString) { | ||||||
| } |     for (const el of document.getElementsByClassName( | ||||||
|  |       "qualityIndicator-39wQDy" | ||||||
|  |     )) { | ||||||
|  |       el.innerHTML = window.discordScreenaudioResolutionString; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }, 500); | ||||||
|  |  | ||||||
| // Fix for broken discord notifications after restart | // Fix for broken discord notifications after restart | ||||||
| // (https://github.com/maltejur/discord-screenaudio/issues/17) | // (https://github.com/maltejur/discord-screenaudio/issues/17) | ||||||
| Notification.requestPermission(); | Notification.requestPermission(); | ||||||
|  |  | ||||||
| setTimeout(() => { |  | ||||||
|   new QWebChannel(qt.webChannelTransport, (channel) => { |  | ||||||
|     userscript = channel.objects.userscript; |  | ||||||
|     main(); |  | ||||||
|   }); |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| function main() { |  | ||||||
|   userscript.muteToggled.connect(() => { |  | ||||||
|     console.log("Toggling mute"); |  | ||||||
|     muteBtn && muteBtn.click(); |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   userscript.deafenToggled.connect(() => { |  | ||||||
|     console.log("Toggling deafen"); |  | ||||||
|     deafenBtn && deafenBtn.click(); |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   userscript.streamStarted.connect((video, width, height, frameRate) => { |  | ||||||
|     resolutionString = video ? `${height}p ${frameRate}FPS` : "Audio Only"; |  | ||||||
|     setGetDisplayMedia(video, { |  | ||||||
|       audio: true, |  | ||||||
|       video: { width, height, frameRate }, |  | ||||||
|     }); |  | ||||||
|     streamStartBtn.click(); |  | ||||||
|     streamStartBtn.style.display = streamStartBtnInitialDisplay; |  | ||||||
|     streamStartBtnClone.remove(); |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   userscript.getPref("disableAutomaticGain", false).then((disabled) => { |  | ||||||
|     autoGainControl = !disabled; |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   function updateUserstyles() { |  | ||||||
|     userscript.log("Loading userstyles..."); |  | ||||||
|     userscript.loadingMessage = "Loading userstyles..."; |  | ||||||
|     let stylesheet = document.getElementById("discordScreenaudioUserstyles"); |  | ||||||
|     if (stylesheet) { |  | ||||||
|       userscript.log("Removing old userstyles..."); |  | ||||||
|       stylesheet.remove(); |  | ||||||
|     } |  | ||||||
|     stylesheet = document.createElement("style"); |  | ||||||
|     stylesheet.id = "discordScreenaudioUserstyles"; |  | ||||||
|     stylesheet.innerText = userscript.userstyles; |  | ||||||
|     document.head.appendChild(stylesheet); |  | ||||||
|     userscript.log("Finished loading userstyles"); |  | ||||||
|     userscript.loadingMessage = ""; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   userscript.userstylesChanged.connect(updateUserstyles); |  | ||||||
|   setTimeout(() => updateUserstyles()); |  | ||||||
|  |  | ||||||
|   setInterval(async () => { |  | ||||||
|     const streamActive = |  | ||||||
|       document.getElementsByClassName("panel-2ZFCRb activityPanel-9icbyU") |  | ||||||
|         .length > 0; |  | ||||||
|  |  | ||||||
|     if (!streamActive && wasStreamActive) userscript.stopVirtmic(); |  | ||||||
|     wasStreamActive = streamActive; |  | ||||||
|  |  | ||||||
|     if (streamActive) { |  | ||||||
|       clonedElements.forEach((el) => { |  | ||||||
|         el.remove(); |  | ||||||
|       }); |  | ||||||
|       clonedElements.length = 0; |  | ||||||
|  |  | ||||||
|       hiddenElements.forEach((el) => { |  | ||||||
|         el.style.display = "block"; |  | ||||||
|       }); |  | ||||||
|       hiddenElements.length = 0; |  | ||||||
|     } else { |  | ||||||
|       for (const el of [ |  | ||||||
|         document.getElementsByClassName("actionButtons-2vEOUh")?.[0] |  | ||||||
|           ?.children[1], |  | ||||||
|         document.querySelector( |  | ||||||
|           ".wrapper-3t3Yqv > div > div > div > div > .controlButton-2PMNom" |  | ||||||
|         ), |  | ||||||
|       ]) { |  | ||||||
|         if (!el) continue; |  | ||||||
|         if (el.classList.contains("discord-screenaudio-cloned")) continue; |  | ||||||
|         streamStartBtn = el; |  | ||||||
|         streamStartBtn.classList.add("discord-screenaudio-cloned"); |  | ||||||
|  |  | ||||||
|         streamStartBtnClone = streamStartBtn.cloneNode(true); |  | ||||||
|         streamStartBtnClone.title = "Share Your Screen with Audio"; |  | ||||||
|         streamStartBtnClone.addEventListener("click", () => { |  | ||||||
|           userscript.showStreamDialog(); |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         streamStartBtnInitialDisplay = streamStartBtn.style.display; |  | ||||||
|  |  | ||||||
|         streamStartBtn.style.display = "none"; |  | ||||||
|         streamStartBtn.parentNode.insertBefore(streamStartBtnClone, el); |  | ||||||
|  |  | ||||||
|         clonedElements.push(streamStartBtnClone); |  | ||||||
|         hiddenElements.push(streamStartBtn); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Add about text in settings |  | ||||||
|     if ( |  | ||||||
|       document.getElementsByClassName("dirscordScreenaudioAboutText").length == |  | ||||||
|       0 |  | ||||||
|     ) { |  | ||||||
|       for (const el of document.getElementsByClassName("info-3pQQBb")) { |  | ||||||
|         let aboutEl; |  | ||||||
|         if (userscript.kxmlgui) { |  | ||||||
|           aboutEl = document.createElement("a"); |  | ||||||
|           aboutEl.addEventListener("click", () => { |  | ||||||
|             userscript.showHelpMenu(); |  | ||||||
|           }); |  | ||||||
|         } else { |  | ||||||
|           aboutEl = document.createElement("div"); |  | ||||||
|         } |  | ||||||
|         aboutEl.innerText = `discord-screenaudio ${userscript.version}`; |  | ||||||
|         aboutEl.style.fontSize = "12px"; |  | ||||||
|         aboutEl.style.color = "var(--text-muted)"; |  | ||||||
|         aboutEl.style.textTransform = "none"; |  | ||||||
|         aboutEl.style.display = "inline-block"; |  | ||||||
|         aboutEl.style.width = "100%"; |  | ||||||
|         aboutEl.classList.add("dirscordScreenaudioAboutText"); |  | ||||||
|         aboutEl.style.cursor = "pointer"; |  | ||||||
|         el.appendChild(aboutEl); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Remove stream settings if stream is active |  | ||||||
|     document.getElementById("manage-streams-change-windows")?.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", () => { |  | ||||||
|           userscript.showShortcutsDialog(); |  | ||||||
|         }) |  | ||||||
|       ); |  | ||||||
|       el.innerHTML = ""; |  | ||||||
|       el.appendChild(div); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const buttonContainer = |  | ||||||
|       document.getElementsByClassName("container-YkUktl")[0]; |  | ||||||
|     if (!buttonContainer) { |  | ||||||
|       userscript.log( |  | ||||||
|         "Cannot locate Mute/Deafen/Settings button container, please report this on GitHub" |  | ||||||
|       ); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     muteBtn = buttonContainer |  | ||||||
|       ? buttonContainer.getElementsByTagName("button")[0] |  | ||||||
|       : null; |  | ||||||
|  |  | ||||||
|     deafenBtn = buttonContainer |  | ||||||
|       ? buttonContainer.getElementsByTagName("button")[1] |  | ||||||
|       : null; |  | ||||||
|  |  | ||||||
|     if (resolutionString) { |  | ||||||
|       for (const el of document.getElementsByClassName( |  | ||||||
|         "qualityIndicator-39wQDy" |  | ||||||
|       )) { |  | ||||||
|         el.innerHTML = resolutionString; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     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", () => { |  | ||||||
|               userscript.showShortcutsDialog(); |  | ||||||
|             }) |  | ||||||
|           ); |  | ||||||
|  |  | ||||||
|           // section.appendChild( |  | ||||||
|           //   createButton("Install Theme", () => { |  | ||||||
|           //     userscript.showThemeDialog(); |  | ||||||
|           //   }) |  | ||||||
|           // ); |  | ||||||
|  |  | ||||||
|           // section.appendChild( |  | ||||||
|           //   createButton("Uninstall Theme", () => { |  | ||||||
|           //     userscript.installUserStyles(""); |  | ||||||
|           //   }) |  | ||||||
|           // ); |  | ||||||
|  |  | ||||||
|           section.appendChild( |  | ||||||
|             createSwitch( |  | ||||||
|               "Move discord-screenaudio to the system tray instead of closing", |  | ||||||
|               await userscript.getBoolPref("trayIcon", false), |  | ||||||
|               (enabled) => { |  | ||||||
|                 userscript.setTrayIcon(enabled); |  | ||||||
|               } |  | ||||||
|             ) |  | ||||||
|           ); |  | ||||||
|  |  | ||||||
|           section.appendChild( |  | ||||||
|             createSwitch( |  | ||||||
|               "Start discord-screenaudio hidden to tray", |  | ||||||
|               await userscript.getPref("startHidden", false), |  | ||||||
|               (hidden) => { |  | ||||||
|                 userscript.setPref("startHidden", hidden); |  | ||||||
|               } |  | ||||||
|             ) |  | ||||||
|           ); |  | ||||||
|  |  | ||||||
|           const divider = document.createElement("div"); |  | ||||||
|           divider.className = "divider-3nqZNm marginTop40-Q4o1tS"; |  | ||||||
|  |  | ||||||
|           firstDivider.after(section); |  | ||||||
|           section.after(divider); |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     for (const el of document.getElementsByClassName("sensitivity-3A7Gs9")) { |  | ||||||
|       if ( |  | ||||||
|         el.getElementsByTagName("div").length > 0 && |  | ||||||
|         !document.getElementById("discord-screenaudio-gaintoggle") |  | ||||||
|       ) { |  | ||||||
|         const toggle = createSwitch( |  | ||||||
|           "Disable automatic gain", |  | ||||||
|           await userscript.getPref("disableAutomaticGain", false), |  | ||||||
|           async (disabled) => { |  | ||||||
|             await userscript.setPref("disableAutomaticGain", disabled); |  | ||||||
|             autoGainControl = !disabled; |  | ||||||
|             setGetUserMedia(); |  | ||||||
|             if (disabled) |  | ||||||
|               userscript.showInformation( |  | ||||||
|                 "discord-screenaudio", |  | ||||||
|                 "If you are currently in a call, this setting may only take effect after you rejoin the call or restart discord-screenaudio." |  | ||||||
|               ); |  | ||||||
|           } |  | ||||||
|         ); |  | ||||||
|         toggle.id = "discord-screenaudio-gaintoggle"; |  | ||||||
|         el.getElementsByTagName("div")[0].appendChild(toggle); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   }, 500); |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -2,8 +2,6 @@ | |||||||
| <RCC> | <RCC> | ||||||
|   <qresource> |   <qresource> | ||||||
|     <file>assets/userscript.js</file> |     <file>assets/userscript.js</file> | ||||||
|     <file>assets/arrpc_bridge_mod.js</file> |  | ||||||
|     <file>assets/arrpc.js</file> |  | ||||||
|     <file>assets/de.shorsh.discord-screenaudio.png</file> |     <file>assets/de.shorsh.discord-screenaudio.png</file> | ||||||
|   </qresource> |   </qresource> | ||||||
| </RCC> | </RCC> | ||||||
| @@ -1,36 +0,0 @@ | |||||||
| #!/usr/bin/bash |  | ||||||
| set -e |  | ||||||
|  |  | ||||||
| cd "$(dirname "$0")/../submodules" |  | ||||||
|  |  | ||||||
| echo_status() { |  | ||||||
|   echo |  | ||||||
|   echo |  | ||||||
|   echo "-> $1..." |  | ||||||
| } |  | ||||||
|  |  | ||||||
| if [ ! -d "arrpc" ]; then |  | ||||||
|   echo_status "Cloning arRPC" |  | ||||||
|   git clone https://github.com/OpenAsar/arrpc.git |  | ||||||
|   cd arrpc |  | ||||||
| else |  | ||||||
|   echo_status "Fetching arRPC changes" |  | ||||||
|   cd arrpc |  | ||||||
|   git fetch |  | ||||||
| fi |  | ||||||
|  |  | ||||||
| echo_status "Checking out latest commit" |  | ||||||
| git reset --hard HEAD |  | ||||||
| git checkout main |  | ||||||
|  |  | ||||||
| echo_status "Installing dependencies" |  | ||||||
| pnpm i -D @vercel/ncc |  | ||||||
|  |  | ||||||
| echo_status "Patching arRPC" |  | ||||||
| sed -i 's/"type": "module",//' package.json |  | ||||||
|  |  | ||||||
| echo_status "Building arRPC" |  | ||||||
| pnpm exec ncc build -m src/index.js |  | ||||||
|  |  | ||||||
| echo_status "Copying built file" |  | ||||||
| cp -v ./dist/index.js ../../assets/arrpc.js |  | ||||||
| @@ -1,84 +0,0 @@ | |||||||
| #include "centralwidget.h" |  | ||||||
| #include "discordpage.h" |  | ||||||
| #include "mainwindow.h" |  | ||||||
|  |  | ||||||
| #include <QWebEngineNotification> |  | ||||||
| #include <QWebEngineProfile> |  | ||||||
| #include <QWebEngineScript> |  | ||||||
| #include <QWebEngineScriptCollection> |  | ||||||
| #include <QWebEngineSettings> |  | ||||||
|  |  | ||||||
| CentralWidget::CentralWidget(QWidget *parent) : QWidget(parent) { |  | ||||||
|   setStyleSheet("background-color:#313338;"); |  | ||||||
|   m_layout = new QVBoxLayout(this); |  | ||||||
|   m_layout->setMargin(0); |  | ||||||
|   m_layout->setSpacing(0); |  | ||||||
|   setupWebView(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void CentralWidget::setupWebView() { |  | ||||||
|   auto page = new DiscordPage(this); |  | ||||||
|  |  | ||||||
|   m_webView = new QWebEngineView(this); |  | ||||||
|   m_webView->setPage(page); |  | ||||||
|  |  | ||||||
|   bool useNotifySend = MainWindow::instance() |  | ||||||
|                            ->settings() |  | ||||||
|                            ->value("useNotifySend", false) |  | ||||||
|                            .toBool(); |  | ||||||
|   if (m_useKF5Notifications || useNotifySend) |  | ||||||
|     QWebEngineProfile::defaultProfile()->setNotificationPresenter( |  | ||||||
|         [&](std::unique_ptr<QWebEngineNotification> notificationInfo) { |  | ||||||
|           if (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 |  | ||||||
|           } |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|   connect(page->userScript(), &UserScript::loadingMessageChanged, this, |  | ||||||
|           &CentralWidget::setLoadingIndicator); |  | ||||||
|  |  | ||||||
|   m_layout->addWidget(m_webView); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void CentralWidget::setLoadingIndicator(QString text) { |  | ||||||
|   if (text != "") { |  | ||||||
|     if (m_loadingLabel == nullptr) { |  | ||||||
|       m_loadingLabel = new QLabel(this); |  | ||||||
|       m_loadingLabel->setMaximumHeight(20); |  | ||||||
|       m_loadingLabel->setAlignment(Qt::AlignHCenter); |  | ||||||
|       m_loadingLabel->setStyleSheet("color:#dedede;"); |  | ||||||
|       m_layout->addWidget(m_loadingLabel); |  | ||||||
|     } |  | ||||||
|     m_loadingLabel->setText(text.mid(0, 100)); |  | ||||||
|   } else { |  | ||||||
|     if (m_loadingLabel != nullptr) { |  | ||||||
|       m_layout->removeWidget(m_loadingLabel); |  | ||||||
|       m_loadingLabel->deleteLater(); |  | ||||||
|       m_loadingLabel = nullptr; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @@ -1,29 +0,0 @@ | |||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include <QLabel> |  | ||||||
| #include <QVBoxLayout> |  | ||||||
| #include <QWebEnginePage> |  | ||||||
| #include <QWebEngineProfile> |  | ||||||
| #include <QWebEngineView> |  | ||||||
| #include <QWidget> |  | ||||||
|  |  | ||||||
| class CentralWidget : public QWidget { |  | ||||||
|   Q_OBJECT |  | ||||||
|  |  | ||||||
| public: |  | ||||||
|   CentralWidget(QWidget *parent = nullptr); |  | ||||||
|  |  | ||||||
| private: |  | ||||||
|   void setupWebView(); |  | ||||||
|   QVBoxLayout *m_layout; |  | ||||||
|   QWebEngineView *m_webView; |  | ||||||
| #ifdef KNOTIFICATIONS |  | ||||||
|   bool m_useKF5Notifications = true; |  | ||||||
| #else |  | ||||||
|   bool m_useKF5Notifications = false; |  | ||||||
| #endif |  | ||||||
|   QLabel *m_loadingLabel = nullptr; |  | ||||||
|  |  | ||||||
| public Q_SLOTS: |  | ||||||
|   void setLoadingIndicator(QString text); |  | ||||||
| }; |  | ||||||
| @@ -3,13 +3,24 @@ | |||||||
| #include "mainwindow.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 <QFileInfo> |  | ||||||
| #include <QMessageBox> | #include <QMessageBox> | ||||||
| #include <QNetworkReply> | #include <QNetworkReply> | ||||||
| #include <QTemporaryFile> |  | ||||||
| #include <QTimer> | #include <QTimer> | ||||||
| #include <QWebChannel> | #include <QWebChannel> | ||||||
| #include <QWebEngineScript> | #include <QWebEngineScript> | ||||||
| @@ -17,31 +28,17 @@ | |||||||
| #include <QWebEngineSettings> | #include <QWebEngineSettings> | ||||||
|  |  | ||||||
| DiscordPage::DiscordPage(QWidget *parent) : QWebEnginePage(parent) { | DiscordPage::DiscordPage(QWidget *parent) : QWebEnginePage(parent) { | ||||||
|   setBackgroundColor(QColor("#313338")); |   setBackgroundColor(QColor("#202225")); | ||||||
|  |   m_virtmicProcess.setProcessChannelMode(QProcess::ForwardedChannels); | ||||||
|  |  | ||||||
|   connect(this, &QWebEnginePage::featurePermissionRequested, this, |   connect(this, &QWebEnginePage::featurePermissionRequested, this, | ||||||
|           &DiscordPage::featurePermissionRequested); |           &DiscordPage::featurePermissionRequested); | ||||||
|   connect(this, &DiscordPage::fullScreenRequested, MainWindow::instance(), |  | ||||||
|           &MainWindow::fullScreenRequested); |  | ||||||
|  |  | ||||||
|   setupPermissions(); |   connect(this, &QWebEnginePage::loadStarted, [=]() { | ||||||
|  |     runJavaScript(QString("window.discordScreenaudioVersion = '%1';") | ||||||
|  |                       .arg(QApplication::applicationVersion())); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|   injectFile(&DiscordPage::injectScript, "qwebchannel.js", |  | ||||||
|              ":/qtwebchannel/qwebchannel.js"); |  | ||||||
|  |  | ||||||
|   setUrl(QUrl("https://discord.com/app")); |  | ||||||
|  |  | ||||||
|   setWebChannel(new QWebChannel(this)); |  | ||||||
|   webChannel()->registerObject("userscript", &m_userScript); |  | ||||||
|  |  | ||||||
|   injectFile(&DiscordPage::injectScript, "userscript.js", |  | ||||||
|              ":/assets/userscript.js"); |  | ||||||
|  |  | ||||||
|   setupUserStyles(); |  | ||||||
|   setupArrpc(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void DiscordPage::setupPermissions() { |  | ||||||
|   settings()->setAttribute(QWebEngineSettings::ScreenCaptureEnabled, true); |   settings()->setAttribute(QWebEngineSettings::ScreenCaptureEnabled, true); | ||||||
|   settings()->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, true); |   settings()->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, true); | ||||||
|   settings()->setAttribute(QWebEngineSettings::AllowRunningInsecureContent, |   settings()->setAttribute(QWebEngineSettings::AllowRunningInsecureContent, | ||||||
| @@ -53,87 +50,71 @@ void DiscordPage::setupPermissions() { | |||||||
|                            false); |                            false); | ||||||
|   settings()->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, false); |   settings()->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, false); | ||||||
|   settings()->setAttribute(QWebEngineSettings::ScrollAnimatorEnabled, true); |   settings()->setAttribute(QWebEngineSettings::ScrollAnimatorEnabled, true); | ||||||
|  |  | ||||||
|  |   setUrl(QUrl("https://discord.com/app")); | ||||||
|  |  | ||||||
|  |   injectScriptFile("userscript.js", ":/assets/userscript.js"); | ||||||
|  |  | ||||||
|  |   injectScriptText("version.js", | ||||||
|  |                    QString("window.discordScreenaudioVersion = '%1';") | ||||||
|  |                        .arg(QApplication::applicationVersion())); | ||||||
|  |  | ||||||
|  | #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, | ||||||
|  |           &DiscordPage::startStream); | ||||||
| } | } | ||||||
|  |  | ||||||
| void DiscordPage::setupUserStyles() { | void DiscordPage::injectScriptText(QString name, QString content) { | ||||||
|   qDebug(userstylesLog).noquote() |  | ||||||
|       << "Looking for userstyles in" << m_configLocation.absolutePath(); |  | ||||||
|   m_userStylesFile = |  | ||||||
|       new QFile(m_configLocation.absoluteFilePath("userstyles.css")); |  | ||||||
|   if (m_userStylesFile->exists()) { |  | ||||||
|     qDebug(userstylesLog).noquote() |  | ||||||
|         << "Found userstyles:" << m_userStylesFile->fileName(); |  | ||||||
|     m_userStylesFile->open(QIODevice::ReadOnly); |  | ||||||
|     m_userStylesContent = m_userStylesFile->readAll(); |  | ||||||
|     m_userStylesFile->close(); |  | ||||||
|     fetchUserStyles(); |  | ||||||
|   } |  | ||||||
|   connect(&m_userScript, &UserScript::shouldInstallUserStyles, this, |  | ||||||
|           &DiscordPage::getUserStyles); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const QRegularExpression importRegex( |  | ||||||
|     R"r(@import (?:url\(|)['"]{0,1}(?!.*usrbgs?\.css)([^'"]+?)['"]{0,1}(?:|\));)r"); |  | ||||||
| const QRegularExpression urlRegex( |  | ||||||
|     R"r(url\(['"]{0,1}((?!https:\/\/fonts.gstatic.com)(?!data:)(?!.*\.woff2)(?!.*\.ttf)[^'"]+?)['"]{0,1}\))r"); |  | ||||||
|  |  | ||||||
| void DiscordPage::fetchUserStyles() { |  | ||||||
|   m_userScript.setProperty( |  | ||||||
|       "loadingMessage", "Loading userstyles: Fetching additional resources..."); |  | ||||||
|   bool foundImport = true; |  | ||||||
|   auto match = importRegex.match(m_userStylesContent); |  | ||||||
|   if (!match.hasMatch()) { |  | ||||||
|     foundImport = false; |  | ||||||
|     match = urlRegex.match(m_userStylesContent); |  | ||||||
|   } |  | ||||||
|   if (match.hasMatch()) { |  | ||||||
|     auto url = match.captured(1); |  | ||||||
|     qDebug(userstylesLog) << "Fetching" << url; |  | ||||||
|     m_userScript.setProperty( |  | ||||||
|         "loadingMessage", |  | ||||||
|         QString("Loading userstyles: Fetching %1...").arg(url)); |  | ||||||
|     QNetworkRequest request(url); |  | ||||||
|     auto reply = m_networkAccessManager.get(request); |  | ||||||
|     connect(reply, &QNetworkReply::finished, [=]() { |  | ||||||
|       QByteArray content = ""; |  | ||||||
|       if (reply->error() == QNetworkReply::NoError) { |  | ||||||
|         if (!reply->attribute(QNetworkRequest::RedirectionTargetAttribute) |  | ||||||
|                  .isNull()) |  | ||||||
|           content = |  | ||||||
|               reply->attribute(QNetworkRequest::RedirectionTargetAttribute) |  | ||||||
|                   .toByteArray(); |  | ||||||
|         else |  | ||||||
|           content = reply->readAll(); |  | ||||||
|       } else |  | ||||||
|         qDebug(userstylesLog) << reply->errorString().toUtf8().constData(); |  | ||||||
|       reply->deleteLater(); |  | ||||||
|       m_userStylesContent = m_userStylesContent.replace( |  | ||||||
|           match.captured(0), foundImport |  | ||||||
|                                  ? content |  | ||||||
|                                  : "url(data:application/octet-stream;base64," + |  | ||||||
|                                        content.toBase64() + ")"); |  | ||||||
|       fetchUserStyles(); |  | ||||||
|     }); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   qDebug(userstylesLog) << "Injecting userstyles"; |  | ||||||
|   m_userScript.setProperty("userstyles", m_userStylesContent); |  | ||||||
|   m_userScript.setProperty("loadingMessage", ""); |  | ||||||
|   if (!m_configLocation.exists()) |  | ||||||
|     m_configLocation.mkpath("."); |  | ||||||
|   m_userStylesFile->open(QIODevice::WriteOnly); |  | ||||||
|   m_userStylesFile->write(m_userStylesContent.toUtf8()); |  | ||||||
|   m_userStylesFile->close(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void DiscordPage::getUserStyles(QString url) { |  | ||||||
|   m_userStylesContent = url == "" ? "" : QString("@import url(%1);").arg(url); |  | ||||||
|   fetchUserStyles(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void DiscordPage::injectScript( |  | ||||||
|     QString name, QString content, |  | ||||||
|     QWebEngineScript::InjectionPoint injectionPoint) { |  | ||||||
|   qDebug(mainLog) << "Injecting " << name; |   qDebug(mainLog) << "Injecting " << name; | ||||||
|  |  | ||||||
|   QWebEngineScript script; |   QWebEngineScript script; | ||||||
| @@ -141,36 +122,20 @@ void DiscordPage::injectScript( | |||||||
|   script.setSourceCode(content); |   script.setSourceCode(content); | ||||||
|   script.setName(name); |   script.setName(name); | ||||||
|   script.setWorldId(QWebEngineScript::MainWorld); |   script.setWorldId(QWebEngineScript::MainWorld); | ||||||
|   script.setInjectionPoint(injectionPoint); |   script.setInjectionPoint(QWebEngineScript::DocumentCreation); | ||||||
|   script.setRunsOnSubFrames(false); |   script.setRunsOnSubFrames(false); | ||||||
|  |  | ||||||
|   scripts().insert(script); |   scripts().insert(script); | ||||||
| } | } | ||||||
|  |  | ||||||
| void DiscordPage::injectScript(QString name, QString content) { | void DiscordPage::injectScriptFile(QString name, QString source) { | ||||||
|   injectScript(name, content, QWebEngineScript::DocumentCreation); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void DiscordPage::injectStylesheet(QString name, QString content) { |  | ||||||
|   auto script = QString(R"(const stylesheet = document.createElement("style"); |  | ||||||
| stylesheet.id = "%1"; |  | ||||||
| stylesheet.innerText = `%2`; |  | ||||||
| document.head.appendChild(stylesheet); |  | ||||||
| )") |  | ||||||
|                     .arg(name) |  | ||||||
|                     .arg(content); |  | ||||||
|   injectScript(name, script, QWebEngineScript::DocumentReady); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void DiscordPage::injectFile(void (DiscordPage::*inject)(QString, QString), |  | ||||||
|                              QString name, QString source) { |  | ||||||
|   QFile file(source); |   QFile file(source); | ||||||
|  |  | ||||||
|   if (!file.open(QIODevice::ReadOnly)) { |   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(), | ||||||
|            file.errorString().toLatin1().constData()); |            file.errorString().toLatin1().constData()); | ||||||
|   } else { |   } else { | ||||||
|     (this->*inject)(name, file.readAll()); |     injectScriptText(name, file.readAll()); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -181,10 +146,11 @@ void DiscordPage::featurePermissionRequested(const QUrl &securityOrigin, | |||||||
|                        QWebEnginePage::PermissionGrantedByUser); |                        QWebEnginePage::PermissionGrantedByUser); | ||||||
|  |  | ||||||
|   if (feature == QWebEnginePage::Feature::MediaAudioCapture) { |   if (feature == QWebEnginePage::Feature::MediaAudioCapture) { | ||||||
|     if (!m_userScript.isVirtmicRunning()) { |     if (m_virtmicProcess.state() == QProcess::NotRunning) { | ||||||
|       qDebug(virtmicLog) << "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_userScript.startVirtmic("None"); |       m_virtmicProcess.start(QApplication::arguments()[0], | ||||||
|  |                              {"--virtmic", "None"}); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @@ -211,102 +177,78 @@ QWebEnginePage *DiscordPage::createWindow(QWebEnginePage::WebWindowType type) { | |||||||
|   return new ExternalPage; |   return new ExternalPage; | ||||||
| } | } | ||||||
|  |  | ||||||
| const QMap<QString, QString> cssAnsiColorMap = {{"black", "30"}, | void DiscordPage::stopVirtmic() { | ||||||
|                                                 {"red", "31"}, |   if (m_virtmicProcess.state() == QProcess::Running) { | ||||||
|                                                 {"green", "32"}, |     qDebug(virtmicLog) << "Stopping Virtmic"; | ||||||
|                                                 {"yellow", "33"}, |     m_virtmicProcess.kill(); | ||||||
|                                                 {"blue", "34"}, |     m_virtmicProcess.waitForFinished(); | ||||||
|                                                 {"magenta", "35"}, |   } | ||||||
|                                                 {"cyan", "36"}, | } | ||||||
|                                                 {"white", "37"}, |  | ||||||
|                                                 {"gray", "90"}, | void DiscordPage::startVirtmic(QString target) { | ||||||
|                                                 {"bright-red", "91"}, |   if (target != "None") { | ||||||
|                                                 {"bright-green", "92"}, |     qDebug(virtmicLog) << "Starting Virtmic with target" << target; | ||||||
|                                                 {"bright-yellow", "93"}, |     m_virtmicProcess.start(QApplication::arguments()[0], {"--virtmic", target}); | ||||||
|                                                 {"bright-blue", "94"}, |   } | ||||||
|                                                 {"bright-magenta", "95"}, | } | ||||||
|                                                 {"bright-cyan", "96"}, |  | ||||||
|                                                 {"bright-white", "97"}, |  | ||||||
|                                                 {"orange", "38;5;208"}, |  | ||||||
|                                                 {"pink", "38;5;205"}, |  | ||||||
|                                                 {"brown", "38;5;94"}, |  | ||||||
|                                                 {"light-gray", "38;5;251"}, |  | ||||||
|                                                 {"dark-gray", "38;5;239"}, |  | ||||||
|                                                 {"light-red", "38;5;203"}, |  | ||||||
|                                                 {"light-green", "38;5;83"}, |  | ||||||
|                                                 {"light-yellow", "38;5;227"}, |  | ||||||
|                                                 {"light-blue", "38;5;75"}, |  | ||||||
|                                                 {"light-magenta", "38;5;207"}, |  | ||||||
|                                                 {"light-cyan", "38;5;87"}, |  | ||||||
|                                                 {"turquoise", "38;5;80"}, |  | ||||||
|                                                 {"violet", "38;5;92"}, |  | ||||||
|                                                 {"purple", "38;5;127"}, |  | ||||||
|                                                 {"lavender", "38;5;183"}, |  | ||||||
|                                                 {"maroon", "38;5;124"}, |  | ||||||
|                                                 {"beige", "38;5;230"}, |  | ||||||
|                                                 {"olive", "38;5;142"}, |  | ||||||
|                                                 {"indigo", "38;5;54"}, |  | ||||||
|                                                 {"teal", "38;5;30"}, |  | ||||||
|                                                 {"gold", "38;5;220"}, |  | ||||||
|                                                 {"silver", "38;5;7"}, |  | ||||||
|                                                 {"navy", "38;5;17"}, |  | ||||||
|                                                 {"steel", "38;5;188"}, |  | ||||||
|                                                 {"salmon", "38;5;173"}, |  | ||||||
|                                                 {"peach", "38;5;217"}, |  | ||||||
|                                                 {"khaki", "38;5;179"}, |  | ||||||
|                                                 {"coral", "38;5;209"}, |  | ||||||
|                                                 {"crimson", "38;5;160"}}; |  | ||||||
|  |  | ||||||
| void DiscordPage::javaScriptConsoleMessage( | void DiscordPage::javaScriptConsoleMessage( | ||||||
|     QWebEnginePage::JavaScriptConsoleMessageLevel level, const QString &message, |     QWebEnginePage::JavaScriptConsoleMessageLevel level, const QString &message, | ||||||
|     int lineNumber, const QString &sourceID) { |     int lineNumber, const QString &sourceID) { | ||||||
|   auto colorSegments = message.split("%c"); |   if (message == "!discord-screenaudio-start-stream") { | ||||||
|   if (colorSegments[0] != "") { |     if (m_streamDialog.isHidden()) | ||||||
|     for (auto line : colorSegments[0].split("\n")) |       m_streamDialog.setHidden(false); | ||||||
|       qDebug(discordLog) << line.toUtf8().constData(); |     else | ||||||
|   } |       m_streamDialog.activateWindow(); | ||||||
|   for (auto segment : colorSegments.mid(1)) { |     m_streamDialog.updateTargets(); | ||||||
|     auto lines = segment.split("\n"); |   } else if (message == "!discord-screenaudio-stream-stopped") { | ||||||
|     QString ansi; |     stopVirtmic(); | ||||||
|     uint endOfStyles = lines.length(); |   } else if (message == "!discord-screenaudio-about") { | ||||||
|     for (auto line = 1; line < lines.length(); line++) { | #ifdef KXMLGUI | ||||||
|       if (!lines[line].endsWith(";")) { |     m_helpMenu->aboutApplication(); | ||||||
|         endOfStyles = line; | #endif | ||||||
|         break; |   } else if (message == "!discord-screenaudio-keybinds") { | ||||||
|       } | #ifdef KXMLGUI | ||||||
|       if (lines[line] == "font-weight: bold;") | #ifdef KGLOBALACCEL | ||||||
|         ansi += "\033[1m"; |     m_shortcutsDialog->show(); | ||||||
|       else if (lines[line].startsWith("color: ")) { | #else | ||||||
|         auto color = lines[line].mid(7).chopped(1); |     QMessageBox::information(MainWindow::instance(), "discord-screenaudio", | ||||||
|         if (cssAnsiColorMap.find(color) != cssAnsiColorMap.end()) |                              "Keybinds are not supported on this platform " | ||||||
|           ansi += "\033[" + cssAnsiColorMap[color] + "m"; |                              "(KGlobalAccel is not available).", | ||||||
|       } |                              QMessageBox::Ok); | ||||||
|     } | #endif | ||||||
|     if (endOfStyles < lines.length()) | #else | ||||||
|       qDebug(discordLog) << (ansi + lines[0].trimmed() + "\033[0m " + |     QMessageBox::information(MainWindow::instance(), "discord-screenaudio", | ||||||
|                              lines[endOfStyles].trimmed()) |                              "Keybinds are not supported on this platform " | ||||||
|                                 .toUtf8() |                              "(KXmlGui and KGlobalAccel are not available).", | ||||||
|                                 .constData(); |                              QMessageBox::Ok); | ||||||
|     for (auto line : lines.mid(endOfStyles + 1)) { | #endif | ||||||
|       qDebug(discordLog) << line.toUtf8().constData(); |   } else if (message.startsWith("dsa: ")) { | ||||||
|     } |     qDebug(userscriptLog) << message.mid(5).toUtf8().constData(); | ||||||
|  |   } else { | ||||||
|  |     qDebug(discordLog) << message; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| UserScript *DiscordPage::userScript() { return &m_userScript; } | void DiscordPage::startStream(QString target, uint width, uint height, | ||||||
|  |                               uint frameRate) { | ||||||
| void DiscordPage::setupArrpc() { |   stopVirtmic(); | ||||||
|   QFile nodejs("/usr/bin/node"); |   startVirtmic(target); | ||||||
|   if (nodejs.exists()) { |   // Wait a bit for the virtmic to start | ||||||
|     auto arrpcSource = QTemporaryFile::createNativeFile(":/assets/arrpc.js"); |   QTimer::singleShot(target == "None" ? 0 : 200, [=]() { | ||||||
|     qDebug(mainLog).noquote() |     runJavaScript(QString("window.discordScreenaudioStartStream(%1, %2, %3);") | ||||||
|         << "NodeJS found, starting arRPC located at" << arrpcSource->fileName(); |                       .arg(width) | ||||||
|     m_arrpcProcess.setProcessChannelMode(QProcess::ForwardedChannels); |                       .arg(height) | ||||||
|     m_arrpcProcess.setProgram(nodejs.fileName()); |                       .arg(frameRate)); | ||||||
|     m_arrpcProcess.setArguments(QStringList{arrpcSource->fileName()}); |   }); | ||||||
|     m_arrpcProcess.start(); | } | ||||||
|  |  | ||||||
|     injectFile(&DiscordPage::injectScript, "arrpc_bridge_mod.js", | void DiscordPage::toggleMute() { | ||||||
|                ":/assets/arrpc_bridge_mod.js"); |   qDebug(shortcutLog) << "Toggling mute"; | ||||||
|   } |   runJavaScript("window.discordScreenaudioToggleMute();"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DiscordPage::toggleDeafen() { | ||||||
|  |   qDebug(shortcutLog) << "Toggling deafen"; | ||||||
|  |   runJavaScript("window.discordScreenaudioToggleDeafen();"); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,35 +1,34 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include "userscript.h" | #include "streamdialog.h" | ||||||
|  | #include "virtmic.h" | ||||||
|  |  | ||||||
|  | #ifdef KXMLGUI | ||||||
|  | #include <KActionCollection> | ||||||
|  | #include <KHelpMenu> | ||||||
|  | #include <KShortcutsDialog> | ||||||
|  | #endif | ||||||
|  |  | ||||||
| #include <QDir> |  | ||||||
| #include <QFile> |  | ||||||
| #include <QNetworkAccessManager> |  | ||||||
| #include <QProcess> | #include <QProcess> | ||||||
| #include <QStandardPaths> |  | ||||||
| #include <QWebEngineFullScreenRequest> | #include <QWebEngineFullScreenRequest> | ||||||
| #include <QWebEnginePage> | #include <QWebEnginePage> | ||||||
| #include <QWebEngineScript> |  | ||||||
|  |  | ||||||
| class DiscordPage : public QWebEnginePage { | class DiscordPage : public QWebEnginePage { | ||||||
|   Q_OBJECT |   Q_OBJECT | ||||||
|  |  | ||||||
| public: | public: | ||||||
|   explicit DiscordPage(QWidget *parent = nullptr); |   explicit DiscordPage(QWidget *parent = nullptr); | ||||||
|   UserScript *userScript(); |  | ||||||
|  |  | ||||||
| private: | private: | ||||||
|   UserScript m_userScript; |   StreamDialog m_streamDialog; | ||||||
|   QFile *m_userStylesFile; |   QProcess m_virtmicProcess; | ||||||
|   QString m_userStylesContent; | #ifdef KXMLGUI | ||||||
|   QNetworkAccessManager m_networkAccessManager; |   KHelpMenu *m_helpMenu; | ||||||
|   QProcess m_arrpcProcess; | #ifdef KGLOBALACCEL | ||||||
|   const QDir m_configLocation = |   KActionCollection *m_actionCollection; | ||||||
|       QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); |   KShortcutsDialog *m_shortcutsDialog; | ||||||
|   void setupPermissions(); | #endif | ||||||
|   void setupUserStyles(); | #endif | ||||||
|   void setupArrpc(); |  | ||||||
|   void fetchUserStyles(); |  | ||||||
|   bool acceptNavigationRequest(const QUrl &url, |   bool acceptNavigationRequest(const QUrl &url, | ||||||
|                                QWebEnginePage::NavigationType type, |                                QWebEnginePage::NavigationType type, | ||||||
|                                bool isMainFrame) override; |                                bool isMainFrame) override; | ||||||
| @@ -38,19 +37,17 @@ 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 name, QString content, |   void injectScriptText(QString name, QString content); | ||||||
|                     QWebEngineScript::InjectionPoint injectionPoint); |   void injectScriptFile(QString name, QString source); | ||||||
|   void injectScript(QString name, QString content); |   void stopVirtmic(); | ||||||
|   void injectStylesheet(QString name, QString content); |   void startVirtmic(QString target); | ||||||
|   void injectFile(void (DiscordPage::*inject)(QString, QString), QString name, |   void toggleMute(); | ||||||
|                   QString source); |   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); | ||||||
| public Q_SLOTS: |  | ||||||
|   void getUserStyles(QString url); |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // Will immediately get destroyed again but is needed for navigation to | // Will immediately get destroyed again but is needed for navigation to | ||||||
|   | |||||||
| @@ -1,22 +0,0 @@ | |||||||
| #include "localserver.h" |  | ||||||
|  |  | ||||||
| bool isProgramRunning(const QString &program_name) { |  | ||||||
|   QLocalSocket socket; |  | ||||||
|   socket.connectToServer(program_name); |  | ||||||
|   if (socket.waitForConnected()) { |  | ||||||
|     return true; // program is already running |  | ||||||
|   } |  | ||||||
|   return false; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void showErrorMessage(const char *text) { |  | ||||||
|   QMessageBox msgBox; |  | ||||||
|  |  | ||||||
|   msgBox.setIcon(QMessageBox::Critical); |  | ||||||
|   msgBox.setText(text); |  | ||||||
|   msgBox.setStandardButtons(QMessageBox::Ok); |  | ||||||
|   msgBox.setDefaultButton(QMessageBox::Ok); |  | ||||||
|   msgBox.setWindowIcon(QIcon(":assets/de.shorsh.discord-screenaudio.png")); |  | ||||||
|  |  | ||||||
|   msgBox.exec(); |  | ||||||
| } |  | ||||||
| @@ -1,9 +0,0 @@ | |||||||
| #pragma once |  | ||||||
| #include "mainwindow.h" |  | ||||||
|  |  | ||||||
| #include <QLocalServer> |  | ||||||
| #include <QLocalSocket> |  | ||||||
| #include <QMessageBox> |  | ||||||
|  |  | ||||||
| bool isProgramRunning(const QString &program_name); |  | ||||||
| void showErrorMessage(const char *text); |  | ||||||
| @@ -5,4 +5,3 @@ Q_LOGGING_CATEGORY(discordLog, "discord"); | |||||||
| Q_LOGGING_CATEGORY(userscriptLog, "userscript"); | Q_LOGGING_CATEGORY(userscriptLog, "userscript"); | ||||||
| Q_LOGGING_CATEGORY(virtmicLog, "virtmic"); | Q_LOGGING_CATEGORY(virtmicLog, "virtmic"); | ||||||
| Q_LOGGING_CATEGORY(shortcutLog, "shortcut"); | Q_LOGGING_CATEGORY(shortcutLog, "shortcut"); | ||||||
| Q_LOGGING_CATEGORY(userstylesLog, "userstyles"); |  | ||||||
|   | |||||||
| @@ -7,4 +7,3 @@ Q_DECLARE_LOGGING_CATEGORY(discordLog); | |||||||
| Q_DECLARE_LOGGING_CATEGORY(userscriptLog); | Q_DECLARE_LOGGING_CATEGORY(userscriptLog); | ||||||
| Q_DECLARE_LOGGING_CATEGORY(virtmicLog); | Q_DECLARE_LOGGING_CATEGORY(virtmicLog); | ||||||
| Q_DECLARE_LOGGING_CATEGORY(shortcutLog); | Q_DECLARE_LOGGING_CATEGORY(shortcutLog); | ||||||
| Q_DECLARE_LOGGING_CATEGORY(userstylesLog); |  | ||||||
|   | |||||||
							
								
								
									
										26
									
								
								src/main.cpp
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								src/main.cpp
									
									
									
									
									
								
							| @@ -1,4 +1,3 @@ | |||||||
| #include "localserver.h" |  | ||||||
| #include "mainwindow.h" | #include "mainwindow.h" | ||||||
| #include "virtmic.h" | #include "virtmic.h" | ||||||
|  |  | ||||||
| @@ -8,14 +7,10 @@ | |||||||
|  |  | ||||||
| #include <QApplication> | #include <QApplication> | ||||||
| #include <QCommandLineParser> | #include <QCommandLineParser> | ||||||
| #include <QLocalServer> |  | ||||||
| #include <QLocalSocket> |  | ||||||
| #include <QLoggingCategory> | #include <QLoggingCategory> | ||||||
| #include <QMessageBox> |  | ||||||
|  |  | ||||||
| int main(int argc, char *argv[]) { | int main(int argc, char *argv[]) { | ||||||
|   QApplication app(argc, argv); |   QApplication app(argc, argv); | ||||||
|  |  | ||||||
|   QApplication::setApplicationName("discord-screenaudio"); |   QApplication::setApplicationName("discord-screenaudio"); | ||||||
|   QApplication::setWindowIcon( |   QApplication::setWindowIcon( | ||||||
|       QIcon(":assets/de.shorsh.discord-screenaudio.png")); |       QIcon(":assets/de.shorsh.discord-screenaudio.png")); | ||||||
| @@ -35,9 +30,6 @@ int main(int argc, char *argv[]) { | |||||||
|   QCommandLineOption degubOption("remote-debugging", |   QCommandLineOption degubOption("remote-debugging", | ||||||
|                                  "Open Chromium Remote Debugging on port 9222"); |                                  "Open Chromium Remote Debugging on port 9222"); | ||||||
|   parser.addOption(degubOption); |   parser.addOption(degubOption); | ||||||
|   QCommandLineOption notifySendOption( |  | ||||||
|       "notify-send", "Use notify-send instead of QT/KF5 notifications"); |  | ||||||
|   parser.addOption(notifySendOption); |  | ||||||
|  |  | ||||||
|   parser.process(app); |   parser.process(app); | ||||||
|  |  | ||||||
| @@ -46,6 +38,8 @@ int main(int argc, char *argv[]) { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   qputenv("QTWEBENGINE_CHROMIUM_FLAGS", |   qputenv("QTWEBENGINE_CHROMIUM_FLAGS", | ||||||
|  |           "--ignore-gpu-blacklist --enable-gpu-rasterization " | ||||||
|  |           "--enable-native-gpu-memory-buffers --num-raster-threads=4 " | ||||||
|           "--enable-features=WebRTCPipeWireCapturer " + |           "--enable-features=WebRTCPipeWireCapturer " + | ||||||
|               qgetenv("QTWEBENGINE_CHROMIUM_FLAGS")); |               qgetenv("QTWEBENGINE_CHROMIUM_FLAGS")); | ||||||
|  |  | ||||||
| @@ -54,21 +48,7 @@ int main(int argc, char *argv[]) { | |||||||
|             "--remote-debugging-port=9222 " + |             "--remote-debugging-port=9222 " + | ||||||
|                 qgetenv("QTWEBENGINE_CHROMIUM_FLAGS")); |                 qgetenv("QTWEBENGINE_CHROMIUM_FLAGS")); | ||||||
|  |  | ||||||
|   MainWindow w(parser.isSet(notifySendOption)); |   MainWindow w; | ||||||
|  |  | ||||||
|   // Check if discord is already running |  | ||||||
|   QString program_name = "discord-screenaudio"; |  | ||||||
|   if (isProgramRunning(program_name)) { |  | ||||||
|     // if running show error message |  | ||||||
|     showErrorMessage("discord-screenaudio is already running"); |  | ||||||
|     return 1; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // open server so we can check if discord is running |  | ||||||
|   QLocalServer server; |  | ||||||
|   server.listen(program_name); |  | ||||||
|   QObject::connect(&server, &QLocalServer::newConnection, []() {}); |  | ||||||
|  |  | ||||||
|   w.show(); |   w.show(); | ||||||
|  |  | ||||||
|   return app.exec(); |   return app.exec(); | ||||||
|   | |||||||
| @@ -14,34 +14,50 @@ | |||||||
| #include <QPushButton> | #include <QPushButton> | ||||||
| #include <QSpacerItem> | #include <QSpacerItem> | ||||||
| #include <QThread> | #include <QThread> | ||||||
| #include <QTimer> |  | ||||||
| #include <QUrl> | #include <QUrl> | ||||||
| #include <QWebEngineFullScreenRequest> | #include <QWebEngineNotification> | ||||||
|  | #include <QWebEngineProfile> | ||||||
|  | #include <QWebEngineScript> | ||||||
|  | #include <QWebEngineScriptCollection> | ||||||
|  | #include <QWebEngineSettings> | ||||||
| #include <QWidget> | #include <QWidget> | ||||||
|  |  | ||||||
| MainWindow *MainWindow::m_instance = nullptr; | MainWindow *MainWindow::m_instance = nullptr; | ||||||
|  |  | ||||||
| MainWindow::MainWindow(bool useNotifySend, QWidget *parent) | MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { | ||||||
|     : QMainWindow(parent) { |  | ||||||
|   assert(MainWindow::m_instance == nullptr); |   assert(MainWindow::m_instance == nullptr); | ||||||
|   MainWindow::m_instance = this; |   MainWindow::m_instance = this; | ||||||
|   setupSettings(); |   setupWebView(); | ||||||
|   m_settings->setValue("useNotifySend", useNotifySend); |   resize(1000, 700); | ||||||
|   m_centralWidget = new CentralWidget(this); |   showMaximized(); | ||||||
|   setCentralWidget(m_centralWidget); | } | ||||||
|   setupTrayIcon(); |  | ||||||
|   setMinimumSize(800, 300); | void MainWindow::setupWebView() { | ||||||
|   if (m_settings->contains("geometry")) { |   auto page = new DiscordPage(this); | ||||||
|     restoreGeometry(m_settings->value("geometry").toByteArray()); |   connect(page, &QWebEnginePage::fullScreenRequested, this, | ||||||
|   } else { |           &MainWindow::fullScreenRequested); | ||||||
|     resize(1000, 700); |  | ||||||
|     showMaximized(); |   m_webView = new QWebEngineView(this); | ||||||
|   } |   m_webView->setPage(page); | ||||||
|   if (m_settings->value("trayIcon", false).toBool() && |  | ||||||
|       m_settings->value("startHidden", false).toBool()) { | #ifdef KNOTIFICATIONS | ||||||
|     hide(); |   QWebEngineProfile::defaultProfile()->setNotificationPresenter( | ||||||
|     QTimer::singleShot(0, [=]() { hide(); }); |       [&](std::unique_ptr<QWebEngineNotification> notificationInfo) { | ||||||
|   } |         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(); | ||||||
|  |                   activateWindow(); | ||||||
|  |                 }); | ||||||
|  |         notification->sendEvent(); | ||||||
|  |       }); | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |   setCentralWidget(m_webView); | ||||||
| } | } | ||||||
|  |  | ||||||
| void MainWindow::fullScreenRequested( | void MainWindow::fullScreenRequested( | ||||||
| @@ -55,79 +71,6 @@ void MainWindow::fullScreenRequested( | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void MainWindow::setupTrayIcon() { | void MainWindow::closeEvent(QCloseEvent *event) { QApplication::quit(); } | ||||||
|   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("discord-screenaudio", "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 { |  | ||||||
|     m_settings->setValue("geometry", saveGeometry()); |  | ||||||
|     QApplication::quit(); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| MainWindow *MainWindow::instance() { return m_instance; } | MainWindow *MainWindow::instance() { return m_instance; } | ||||||
|  |  | ||||||
| CentralWidget *MainWindow::centralWidget() { |  | ||||||
|   return instance()->m_centralWidget; |  | ||||||
| }; |  | ||||||
|   | |||||||
| @@ -1,39 +1,31 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include "centralwidget.h" | #include "discordpage.h" | ||||||
|  |  | ||||||
| #include <QMainWindow> | #include <QMainWindow> | ||||||
| #include <QMenu> |  | ||||||
| #include <QScopedPointer> | #include <QScopedPointer> | ||||||
| #include <QSettings> |  | ||||||
| #include <QString> | #include <QString> | ||||||
| #include <QSystemTrayIcon> |  | ||||||
| #include <QVBoxLayout> |  | ||||||
| #include <QVector> | #include <QVector> | ||||||
|  | #include <QWebEnginePage> | ||||||
|  | #include <QWebEngineProfile> | ||||||
|  | #include <QWebEngineView> | ||||||
|  |  | ||||||
| class MainWindow : public QMainWindow { | class MainWindow : public QMainWindow { | ||||||
|   Q_OBJECT |   Q_OBJECT | ||||||
|  |  | ||||||
| public: | public: | ||||||
|   explicit MainWindow(bool useNotifySend = false, QWidget *parent = nullptr); |   explicit MainWindow(QWidget *parent = nullptr); | ||||||
|   static MainWindow *instance(); |   static MainWindow *instance(); | ||||||
|   QSettings *settings() const; |  | ||||||
|   static CentralWidget *centralWidget(); |  | ||||||
|  |  | ||||||
| private: | private: | ||||||
|   void setupTrayIcon(); |   void setupWebView(); | ||||||
|   void cleanTrayIcon(); |   QWebEngineView *m_webView; | ||||||
|   void setupSettings(); |  | ||||||
|   QWebEngineProfile *prepareProfile(); |   QWebEngineProfile *prepareProfile(); | ||||||
|  |   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; |   static MainWindow *m_instance; | ||||||
|   CentralWidget *m_centralWidget; |  | ||||||
|  |  | ||||||
| public Q_SLOTS: | private Q_SLOTS: | ||||||
|   void setTrayIcon(bool enabled); |  | ||||||
|   void fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest); |   void fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -9,95 +9,73 @@ | |||||||
| #include <QSizePolicy> | #include <QSizePolicy> | ||||||
| #include <QVBoxLayout> | #include <QVBoxLayout> | ||||||
|  |  | ||||||
| StreamDialog::StreamDialog(QWidget *parent) : QDialog(parent) { | 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); |   auto targetLabel = new QLabel(this); | ||||||
|     m_videoGroupBox->setTitle("Video"); |   targetLabel->setText("Which app do you want to stream sound from?"); | ||||||
|     m_videoGroupBox->setCheckable(true); |   layout->addWidget(targetLabel); | ||||||
|     layout->addWidget(m_videoGroupBox); |  | ||||||
|  |  | ||||||
|     { |   auto targetHBox = new QHBoxLayout(this); | ||||||
|       auto videoLayout = new QVBoxLayout(m_videoGroupBox); |   layout->addLayout(targetHBox); | ||||||
|  |  | ||||||
|       auto resolutionLabel = new QLabel(this); |   m_targetComboBox = new QComboBox(this); | ||||||
|       resolutionLabel->setText("Resolution"); |   updateTargets(); | ||||||
|       videoLayout->addWidget(resolutionLabel); |   targetHBox->addWidget(m_targetComboBox); | ||||||
|  |  | ||||||
|       m_resolutionComboBox = new QComboBox(this); |   auto refreshTargetsButton = new QPushButton(this); | ||||||
|       m_resolutionComboBox->addItem("2160p", "3840x2160"); |   refreshTargetsButton->setFixedSize(30, 30); | ||||||
|       m_resolutionComboBox->addItem("1440p", "2560x1440"); |   refreshTargetsButton->setIcon(QIcon::fromTheme("view-refresh")); | ||||||
|       m_resolutionComboBox->addItem("1080p", "1920x1080"); |   connect(refreshTargetsButton, &QPushButton::clicked, this, | ||||||
|       m_resolutionComboBox->addItem("720p", "1280x720"); |           &StreamDialog::updateTargets); | ||||||
|       m_resolutionComboBox->addItem("480p", "854x480"); |   targetHBox->addWidget(refreshTargetsButton); | ||||||
|       m_resolutionComboBox->addItem("360p", "640x360"); |  | ||||||
|       m_resolutionComboBox->addItem("240p", "426x240"); |  | ||||||
|       m_resolutionComboBox->setCurrentText("720p"); |  | ||||||
|       videoLayout->addWidget(m_resolutionComboBox); |  | ||||||
|  |  | ||||||
|       auto framerateLabel = new QLabel(this); |   auto qualityLabel = new QLabel(this); | ||||||
|       framerateLabel->setText("Framerate"); |   qualityLabel->setText("Stream Quality"); | ||||||
|       videoLayout->addWidget(framerateLabel); |   layout->addWidget(qualityLabel); | ||||||
|  |  | ||||||
|       m_framerateComboBox = new QComboBox(this); |   auto qualityHBox = new QHBoxLayout(this); | ||||||
|       m_framerateComboBox->addItem("144 FPS", 144); |   layout->addLayout(qualityHBox); | ||||||
|       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_qualityResolutionComboBox = new QComboBox(this); | ||||||
|     m_audioGroupBox->setCheckable(true); |   m_qualityResolutionComboBox->addItem("2160p", "3840x2160"); | ||||||
|     m_audioGroupBox->setTitle("Audio"); |   m_qualityResolutionComboBox->addItem("1440p", "2560x1440"); | ||||||
|     layout->addWidget(m_audioGroupBox); |   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); | ||||||
|       auto audioLayout = new QVBoxLayout(m_audioGroupBox); |   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 targetLabel = new QLabel(this); |   auto button = new QPushButton(this); | ||||||
|       targetLabel->setText("Audio Source"); |   button->setText("Start Stream"); | ||||||
|       audioLayout->addWidget(targetLabel); |   connect(button, &QPushButton::clicked, this, &StreamDialog::startStream); | ||||||
|  |   layout->addWidget(button, Qt::AlignRight | Qt::AlignBottom); | ||||||
|  |  | ||||||
|       { |   setLayout(layout); | ||||||
|         auto targetLayout = new QHBoxLayout(); |  | ||||||
|         audioLayout->addLayout(targetLayout); |  | ||||||
|  |  | ||||||
|         m_targetComboBox = new QComboBox(this); |  | ||||||
|         updateTargets(); |  | ||||||
|         targetLayout->addWidget(m_targetComboBox); |  | ||||||
|  |  | ||||||
|         auto refreshTargetsButton = new QPushButton(this); |  | ||||||
|         refreshTargetsButton->setFixedSize(30, 30); |  | ||||||
|         refreshTargetsButton->setIcon(QIcon::fromTheme("view-refresh")); |  | ||||||
|         connect(refreshTargetsButton, &QPushButton::clicked, this, |  | ||||||
|                 &StreamDialog::updateTargets); |  | ||||||
|         targetLayout->addWidget(refreshTargetsButton); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     auto button = new QPushButton(this); |  | ||||||
|     button->setText("Start Stream"); |  | ||||||
|     connect(button, &QPushButton::clicked, this, &StreamDialog::startStream); |  | ||||||
|     layout->addWidget(button, Qt::AlignRight | Qt::AlignBottom); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   setWindowTitle("discord-screenaudio Stream Dialog"); |   setWindowTitle("discord-screenaudio Stream Dialog"); | ||||||
| } | } | ||||||
|  |  | ||||||
| void StreamDialog::startStream() { | void StreamDialog::startStream() { | ||||||
|   auto resolution = m_resolutionComboBox->currentData().toString().split('x'); |   auto resolution = | ||||||
|   emit requestedStreamStart(m_videoGroupBox->isChecked(), |       m_qualityResolutionComboBox->currentData().toString().split('x'); | ||||||
|                             m_audioGroupBox->isChecked(), resolution[0].toInt(), |   emit requestedStreamStart(m_targetComboBox->currentText(), | ||||||
|                             resolution[1].toInt(), |                             resolution[0].toUInt(), resolution[1].toUInt(), | ||||||
|                             m_framerateComboBox->currentData().toInt(), |                             m_qualityFPSComboBox->currentData().toUInt()); | ||||||
|                             m_targetComboBox->currentText()); |  | ||||||
|   setHidden(true); |   setHidden(true); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -105,6 +83,7 @@ void StreamDialog::updateTargets() { | |||||||
|   auto lastTarget = m_targetComboBox->currentText(); |   auto lastTarget = m_targetComboBox->currentText(); | ||||||
|  |  | ||||||
|   m_targetComboBox->clear(); |   m_targetComboBox->clear(); | ||||||
|  |   m_targetComboBox->addItem("[None]"); | ||||||
|   m_targetComboBox->addItem("[All Desktop Audio]"); |   m_targetComboBox->addItem("[All Desktop Audio]"); | ||||||
|   for (auto target : Virtmic::getTargets()) { |   for (auto target : Virtmic::getTargets()) { | ||||||
|     m_targetComboBox->addItem(target); |     m_targetComboBox->addItem(target); | ||||||
|   | |||||||
| @@ -2,25 +2,22 @@ | |||||||
|  |  | ||||||
| #include <QComboBox> | #include <QComboBox> | ||||||
| #include <QDialog> | #include <QDialog> | ||||||
| #include <QGroupBox> |  | ||||||
| #include <QWidget> | #include <QWidget> | ||||||
|  |  | ||||||
| class StreamDialog : public QDialog { | class StreamDialog : public QWidget { | ||||||
|   Q_OBJECT |   Q_OBJECT | ||||||
|  |  | ||||||
| public: | public: | ||||||
|   explicit StreamDialog(QWidget *parent = nullptr); |   explicit StreamDialog(); | ||||||
|  |  | ||||||
| private: | private: | ||||||
|   QComboBox *m_targetComboBox; |   QComboBox *m_targetComboBox; | ||||||
|   QComboBox *m_resolutionComboBox; |   QComboBox *m_qualityResolutionComboBox; | ||||||
|   QComboBox *m_framerateComboBox; |   QComboBox *m_qualityFPSComboBox; | ||||||
|   QGroupBox *m_videoGroupBox; |  | ||||||
|   QGroupBox *m_audioGroupBox; |  | ||||||
|  |  | ||||||
| Q_SIGNALS: | Q_SIGNALS: | ||||||
|   void requestedStreamStart(bool video, bool audio, int width, int height, |   void requestedStreamStart(QString target, uint width, uint height, | ||||||
|                             int frameRate, QString target); |                             uint frameRate); | ||||||
|  |  | ||||||
| public Q_SLOTS: | public Q_SLOTS: | ||||||
|   void updateTargets(); |   void updateTargets(); | ||||||
|   | |||||||
| @@ -1,188 +0,0 @@ | |||||||
| #include "userscript.h" |  | ||||||
| #include "log.h" |  | ||||||
| #include "mainwindow.h" |  | ||||||
|  |  | ||||||
| #include <QApplication> |  | ||||||
| #include <QDebug> |  | ||||||
| #include <QFile> |  | ||||||
| #include <QInputDialog> |  | ||||||
| #include <QMessageBox> |  | ||||||
| #include <QStandardPaths> |  | ||||||
| #include <QTimer> |  | ||||||
|  |  | ||||||
| #ifdef KXMLGUI |  | ||||||
| #include <KActionCollection> |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| UserScript::UserScript() : QObject() { |  | ||||||
|   setupHelpMenu(); |  | ||||||
|   setupShortcutsDialog(); |  | ||||||
|   setupStreamDialog(); |  | ||||||
|   setupVirtmic(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void UserScript::setupHelpMenu() { |  | ||||||
| #ifdef KXMLGUI |  | ||||||
|   m_kxmlgui = 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"); |  | ||||||
|   aboutData.addComponent("arRPC", |  | ||||||
|                          "An open implementation of Discord's local RPC " |  | ||||||
|                          "servers<br>Copyright (c) 2022 OpenAsar", |  | ||||||
|                          nullptr, "https://github.com/OpenAsar/arrpc"); |  | ||||||
|   m_helpMenu = new KHelpMenu(MainWindow::instance(), aboutData); |  | ||||||
| #endif |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void UserScript::setupShortcutsDialog() { |  | ||||||
| #ifdef KXMLGUI |  | ||||||
| #ifdef KGLOBALACCEL |  | ||||||
|   m_kglobalaccel = true; |  | ||||||
|  |  | ||||||
|   auto toggleMuteAction = new QAction(this); |  | ||||||
|   toggleMuteAction->setText("Toggle Mute"); |  | ||||||
|   toggleMuteAction->setIcon(QIcon::fromTheme("microphone-sensitivity-muted")); |  | ||||||
|   connect(toggleMuteAction, &QAction::triggered, this, |  | ||||||
|           &UserScript::muteToggled); |  | ||||||
|  |  | ||||||
|   auto toggleDeafenAction = new QAction(this); |  | ||||||
|   toggleDeafenAction->setText("Toggle Deafen"); |  | ||||||
|   toggleDeafenAction->setIcon(QIcon::fromTheme("audio-volume-muted")); |  | ||||||
|   connect(toggleDeafenAction, &QAction::triggered, this, |  | ||||||
|           &UserScript::deafenToggled); |  | ||||||
|  |  | ||||||
|   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 |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void UserScript::setupStreamDialog() { |  | ||||||
|   m_streamDialog = new StreamDialog(MainWindow::instance()); |  | ||||||
|   connect(m_streamDialog, &StreamDialog::requestedStreamStart, this, |  | ||||||
|           &UserScript::startStream); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void UserScript::setupVirtmic() { |  | ||||||
|   m_virtmicProcess.setProcessChannelMode(QProcess::ForwardedChannels); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| bool UserScript::isVirtmicRunning() { |  | ||||||
|   return m_virtmicProcess.state() != QProcess::NotRunning; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| QString UserScript::version() { return QApplication::applicationVersion(); } |  | ||||||
|  |  | ||||||
| QVariant UserScript::getPref(QString name, QVariant fallback) { |  | ||||||
|   return MainWindow::instance()->settings()->value(name, fallback); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| bool UserScript::getBoolPref(QString name, bool fallback) { |  | ||||||
|   return getPref(name, fallback).toBool(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void UserScript::setPref(QString name, QVariant value) { |  | ||||||
|   return MainWindow::instance()->settings()->setValue(name, value); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void UserScript::setTrayIcon(bool value) { |  | ||||||
|   setPref("trayIcon", value); |  | ||||||
|   MainWindow::instance()->setTrayIcon(value); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void UserScript::log(QString message) { |  | ||||||
|   qDebug(userscriptLog) << message.toUtf8().constData(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void UserScript::showShortcutsDialog() { |  | ||||||
| #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 |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void UserScript::showHelpMenu() { |  | ||||||
| #ifdef KXMLGUI |  | ||||||
|   m_helpMenu->aboutApplication(); |  | ||||||
| #endif |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void UserScript::stopVirtmic() { |  | ||||||
|   if (m_virtmicProcess.state() == QProcess::Running) { |  | ||||||
|     qDebug(virtmicLog) << "Stopping Virtmic"; |  | ||||||
|     m_virtmicProcess.kill(); |  | ||||||
|     m_virtmicProcess.waitForFinished(); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void UserScript::startVirtmic(QString target) { |  | ||||||
|   qDebug(virtmicLog) << "Starting Virtmic with target" << target; |  | ||||||
|   m_virtmicProcess.start(QApplication::arguments()[0], {"--virtmic", target}); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void UserScript::startStream(bool video, bool audio, int width, int height, |  | ||||||
|                              int frameRate, QString target) { |  | ||||||
|   stopVirtmic(); |  | ||||||
|   startVirtmic(audio ? target : "[None]"); |  | ||||||
|   // Wait a bit for the virtmic to start |  | ||||||
|   QTimer::singleShot( |  | ||||||
|       200, [=]() { emit streamStarted(video, width, height, frameRate); }); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void UserScript::showStreamDialog() { |  | ||||||
|   if (m_streamDialog->isHidden()) |  | ||||||
|     m_streamDialog->setHidden(false); |  | ||||||
|   else |  | ||||||
|     m_streamDialog->activateWindow(); |  | ||||||
|   m_streamDialog->updateTargets(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void UserScript::showThemeDialog() { |  | ||||||
|   auto url = QInputDialog::getText(MainWindow::instance(), "Theme Installation", |  | ||||||
|                                    "Please enter the URL of the Theme"); |  | ||||||
|   if (url != "") |  | ||||||
|     emit shouldInstallUserStyles(url); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void UserScript::installUserStyles(QString url) { |  | ||||||
|   emit shouldInstallUserStyles(url); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void UserScript::showInformation(QString title, QString message) { |  | ||||||
|   QMessageBox::information(MainWindow::instance(), title, message); |  | ||||||
| } |  | ||||||
| @@ -1,84 +0,0 @@ | |||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include "streamdialog.h" |  | ||||||
|  |  | ||||||
| #include <QObject> |  | ||||||
| #include <QProcess> |  | ||||||
|  |  | ||||||
| #ifdef KXMLGUI |  | ||||||
| #include <KAboutData> |  | ||||||
| #include <KHelpMenu> |  | ||||||
| #include <KShortcutsDialog> |  | ||||||
| #include <KXmlGuiWindow> |  | ||||||
| #include <QAction> |  | ||||||
|  |  | ||||||
| #ifdef KGLOBALACCEL |  | ||||||
| #include <KGlobalAccel> |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| #ifdef KNOTIFICATIONS |  | ||||||
| #include <KNotification> |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| class UserScript : public QObject { |  | ||||||
|   Q_OBJECT |  | ||||||
|  |  | ||||||
| public: |  | ||||||
|   UserScript(); |  | ||||||
|   bool isVirtmicRunning(); |  | ||||||
|   Q_PROPERTY(QString version READ version CONSTANT); |  | ||||||
|   Q_PROPERTY(bool kxmlgui MEMBER m_kxmlgui CONSTANT); |  | ||||||
|   Q_PROPERTY(bool kglobalaccel MEMBER m_kglobalaccel CONSTANT); |  | ||||||
|   Q_PROPERTY(QString userstyles MEMBER m_userstyles NOTIFY userstylesChanged); |  | ||||||
|   Q_PROPERTY(QString loadingMessage MEMBER m_loadingMessage NOTIFY |  | ||||||
|                  loadingMessageChanged); |  | ||||||
|  |  | ||||||
| private: |  | ||||||
|   QProcess m_virtmicProcess; |  | ||||||
|   StreamDialog *m_streamDialog; |  | ||||||
|   bool m_kxmlgui = false; |  | ||||||
|   bool m_kglobalaccel = false; |  | ||||||
|   QString m_userstyles; |  | ||||||
|   QString m_loadingMessage; |  | ||||||
| #ifdef KXMLGUI |  | ||||||
|   KHelpMenu *m_helpMenu; |  | ||||||
| #ifdef KGLOBALACCEL |  | ||||||
|   KActionCollection *m_actionCollection; |  | ||||||
|   KShortcutsDialog *m_shortcutsDialog; |  | ||||||
| #endif |  | ||||||
| #endif |  | ||||||
|   void setupHelpMenu(); |  | ||||||
|   void setupShortcutsDialog(); |  | ||||||
|   void setupStreamDialog(); |  | ||||||
|   void setupVirtmic(); |  | ||||||
|  |  | ||||||
| Q_SIGNALS: |  | ||||||
|   void muteToggled(); |  | ||||||
|   void deafenToggled(); |  | ||||||
|   void streamStarted(bool video, int width, int height, int frameRate); |  | ||||||
|   void userstylesChanged(); |  | ||||||
|   void loadingMessageChanged(QString message); |  | ||||||
|   void shouldInstallUserStyles(QString url); |  | ||||||
|  |  | ||||||
| public Q_SLOTS: |  | ||||||
|   void log(QString message); |  | ||||||
|   QString version(); |  | ||||||
|   QVariant getPref(QString name, QVariant fallback); |  | ||||||
|   bool getBoolPref(QString name, bool fallback); |  | ||||||
|   void setPref(QString name, QVariant value); |  | ||||||
|   void setTrayIcon(bool value); |  | ||||||
|   void showShortcutsDialog(); |  | ||||||
|   void showHelpMenu(); |  | ||||||
|   void showStreamDialog(); |  | ||||||
|   void showInformation(QString title, QString message); |  | ||||||
|   void stopVirtmic(); |  | ||||||
|   void startVirtmic(QString target); |  | ||||||
|   void showThemeDialog(); |  | ||||||
|   void installUserStyles(QString url); |  | ||||||
|  |  | ||||||
| private Q_SLOTS: |  | ||||||
|   void startStream(bool video, bool audio, int width, int height, int frameRate, |  | ||||||
|                    QString target); |  | ||||||
| }; |  | ||||||
| @@ -8,25 +8,6 @@ namespace Virtmic { | |||||||
|  |  | ||||||
| const QStringList EXCLUDE_TARGETS{"Chromium input", "discord-screenaudio"}; | const QStringList EXCLUDE_TARGETS{"Chromium input", "discord-screenaudio"}; | ||||||
|  |  | ||||||
| const std::string nullstr = ""; |  | ||||||
| const std::string &getTarget(const pipewire::spa::dict &props) { |  | ||||||
|   if (props.count("media.class") && |  | ||||||
|       props.at("media.class") == "Stream/Output/Audio") { |  | ||||||
|     if (props.count("application.name") && props.at("application.name") != "") |  | ||||||
|       return props.at("application.name"); |  | ||||||
|     else if (props.count("application.process.binary") && |  | ||||||
|              props.at("application.process.binary") != "") |  | ||||||
|       return props.at("application.process.binary"); |  | ||||||
|     else |  | ||||||
|       return props.at("node.name"); |  | ||||||
|   } else |  | ||||||
|     return nullstr; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| QString qGetTarget(const pipewire::spa::dict &props) { |  | ||||||
|   return QString::fromStdString(getTarget(props)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| 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); | ||||||
| @@ -41,7 +22,8 @@ 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 = qGetTarget(info.props); |           auto name = QString::fromStdString(info.props["application.name"]); | ||||||
|  |  | ||||||
|           if (name != "" && !EXCLUDE_TARGETS.contains(name) && |           if (name != "" && !EXCLUDE_TARGETS.contains(name) && | ||||||
|               !targets.contains(name)) { |               !targets.contains(name)) { | ||||||
|             targets.append(name); |             targets.append(name); | ||||||
| @@ -85,7 +67,8 @@ void start(QString _target) { | |||||||
|         continue; |         continue; | ||||||
|  |  | ||||||
|       auto &parent = nodes.at(parent_id); |       auto &parent = nodes.at(parent_id); | ||||||
|       std::string name = getTarget(parent.props); |       auto name = parent.props["application.name"]; | ||||||
|  |  | ||||||
|       if (name == target || |       if (name == target || | ||||||
|           (target == "[All Desktop Audio]" && |           (target == "[All Desktop Audio]" && | ||||||
|            !EXCLUDE_TARGETS.contains(QString::fromStdString(name)))) { |            !EXCLUDE_TARGETS.contains(QString::fromStdString(name)))) { | ||||||
| @@ -95,7 +78,8 @@ void start(QString _target) { | |||||||
|             core.create<pipewire::link_factory>( |             core.create<pipewire::link_factory>( | ||||||
|                 {fl ? virt_fl->info().id : virt_fr->info().id, port_id})); |                 {fl ? virt_fl->info().id : virt_fr->info().id, port_id})); | ||||||
|         qDebug(virtmicLog) << QString("Link: %1:%2 -> %3") |         qDebug(virtmicLog) << QString("Link: %1:%2 -> %3") | ||||||
|                                   .arg(QString::fromStdString(name)) |                                   .arg(QString::fromStdString( | ||||||
|  |                                       parent.props["application.name"])) | ||||||
|                                   .arg(port_id) |                                   .arg(port_id) | ||||||
|                                   .arg(fl ? virt_fl->info().id |                                   .arg(fl ? virt_fl->info().id | ||||||
|                                           : virt_fr->info().id) |                                           : virt_fr->info().id) | ||||||
| @@ -105,7 +89,7 @@ void start(QString _target) { | |||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   std::string target = _target.toUtf8().toStdString(); |   std::string target = _target.toLatin1().toStdString(); | ||||||
|  |  | ||||||
|   auto virtual_mic = core.create("adapter", |   auto virtual_mic = core.create("adapter", | ||||||
|                                  {{"node.name", "discord-screenaudio-virtmic"}, |                                  {{"node.name", "discord-screenaudio-virtmic"}, | ||||||
| @@ -128,12 +112,11 @@ 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); | ||||||
|           auto info = node.info(); |           if (!node.info().props.count("application.name")) | ||||||
|           std::string name = getTarget(info.props); |  | ||||||
|           if (name == nullstr) |  | ||||||
|             return; |             return; | ||||||
|           qDebug(virtmicLog) << QString("Added: %1") |           qDebug(virtmicLog) << QString("Added: %1") | ||||||
|                                     .arg(QString::fromStdString(name)) |                                     .arg(QString::fromStdString( | ||||||
|  |                                         node.info().props["application.name"])) | ||||||
|                                     .toUtf8() |                                     .toUtf8() | ||||||
|                                     .data(); |                                     .data(); | ||||||
|  |  | ||||||
| @@ -169,11 +152,9 @@ 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::string name = getTarget(info.props); |  | ||||||
|           if (name == nullstr) |  | ||||||
|             return; |  | ||||||
|           qDebug(virtmicLog) << QString("Removed: %1") |           qDebug(virtmicLog) << QString("Removed: %1") | ||||||
|                                     .arg(QString::fromStdString(name)) |                                     .arg(QString::fromStdString( | ||||||
|  |                                         info.props["application.name"].data())) | ||||||
|                                     .toUtf8() |                                     .toUtf8() | ||||||
|                                     .data(); |                                     .data(); | ||||||
|           nodes.erase(id); |           nodes.erase(id); | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								submodules/Vencord
									
									
									
									
									
										Submodule
									
								
							
							
								
								
								
								
								
							
						
						
									
										1
									
								
								submodules/Vencord
									
									
									
									
									
										Submodule
									
								
							 Submodule submodules/Vencord added at 0d996633f2
									
								
							
		Reference in New Issue
	
	Block a user