Compare commits
	
		
			33 Commits
		
	
	
		
			vencord
			...
			virtmic-re
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | e6449ad807 | ||
|  | e919e3ea25 | ||
|  | 10fff86ca4 | ||
|  | 87e84dec5c | ||
|  | 4b782133ae | ||
|  | d5257207bc | ||
|  | 423884ae0c | ||
|  | 7ea3d0aab1 | ||
|  | 18b15f5cf4 | ||
|  | 96bca52d0d | ||
|  | 3540774c82 | ||
|  | b5435acdd8 | ||
|  | 38c2b92e2d | ||
|  | e447206082 | ||
|  | f31901073e | ||
|  | 77f31b63aa | ||
|  | 0b9fc100c2 | ||
|  | f73524de27 | ||
|  | 8a8690fe53 | ||
|  | 259e6dc75d | ||
|  | 9cbdca0441 | ||
|  | a9ddc0216f | ||
|  | b39e23d462 | ||
|  | 331fb8f4ca | ||
|  | 335a4456ed | ||
|  | 26a2ca7b05 | ||
|  | 30c0526ff7 | ||
|  | 3119e1df19 | ||
|  | 63180f5d53 | ||
|  | 78d43991ab | ||
|  | 193b69f45f | ||
|  | 485ff9634b | ||
|  | 74fdef683f | 
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +1,3 @@ | |||||||
| /build | /build | ||||||
| .vscode | .vscode | ||||||
| /submodules/Vencord | /submodules/arrpc | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +1,6 @@ | |||||||
| [submodule "submodules/rohrkabel"] | [submodule "submodules/rohrkabel"] | ||||||
| 	path = submodules/rohrkabel | 	path = submodules/rohrkabel | ||||||
| 	url = https://github.com/Soundux/rohrkabel | 	url = https://github.com/Soundux/rohrkabel | ||||||
|  | [submodule "submodules/channel"] | ||||||
|  | 	path = submodules/channel | ||||||
|  | 	url = https://github.com/Soundux/channel.git | ||||||
|   | |||||||
| @@ -44,6 +44,7 @@ set(discord-screenaudio_SRC | |||||||
|   src/streamdialog.cpp |   src/streamdialog.cpp | ||||||
|   src/log.cpp |   src/log.cpp | ||||||
|   src/userscript.cpp |   src/userscript.cpp | ||||||
|  |   src/centralwidget.cpp | ||||||
|   resources.qrc |   resources.qrc | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -62,15 +63,20 @@ if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git") | |||||||
|   endif() |   endif() | ||||||
| endif() | endif() | ||||||
|  |  | ||||||
| if(NOT EXISTS "${PROJECT_SOURCE_DIR}/submodules/rohrkabel/CMakeLists.txt") | function(add_git_subdirectory SUBMODULE) | ||||||
|   message(FATAL_ERROR "Rohrkabel was not found since you are not in a Git checkout or have GIT_SUBMODULE disabled. Please provide rohrkabel manually to `./submodules/rohrkabel`.") |   if(NOT EXISTS "${PROJECT_SOURCE_DIR}/submodules/${SUBMODULE}/CMakeLists.txt") | ||||||
| endif() |     message(FATAL_ERROR "Submodule ${SUBMODULE} was not found since you are not in a Git checkout or have GIT_SUBMODULE disabled. Please provide ${SUBMODULE} manually to `./submodules/${SUBMODULE}`.") | ||||||
|  |   endif() | ||||||
|  |  | ||||||
| add_subdirectory(submodules/rohrkabel) |   add_subdirectory(submodules/${SUBMODULE}) | ||||||
|  | endfunction() | ||||||
|  |  | ||||||
|  | add_git_subdirectory(rohrkabel) | ||||||
|  | add_git_subdirectory(channel) | ||||||
|  |  | ||||||
| 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 Qt::Widgets Qt::WebEngineWidgets rohrkabel channel) | ||||||
|  |  | ||||||
| if(KF5Notifications_FOUND) | if(KF5Notifications_FOUND) | ||||||
|   target_link_libraries(discord-screenaudio KF5::Notifications) |   target_link_libraries(discord-screenaudio KF5::Notifications) | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								README.md
									
									
									
									
									
								
							| @@ -9,7 +9,7 @@ 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-it-works)). | ([see explanation](#how-does-this-work)). | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -106,9 +106,11 @@ allowing access to "All system files" under the "Filesystem" section. | |||||||
| ### Is there any way to add custom CSS / a theme? | ### Is there any way to add custom CSS / a theme? | ||||||
|  |  | ||||||
| Yes, you can add all your styles into | Yes, you can add all your styles into | ||||||
| `~/.config/discord-screenaudio/userstyles.css`. But please note that due to | `~/.config/discord-screenaudio/userstyles.css` (or | ||||||
| QtWebEngine limitations concerning content security policies, you can't use any | `~/.var/app/de.shorsh.discord-screenaudio/config/discord-screenaudio/userstyles.css` | ||||||
| external files (like `@import` or `url()`). | 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 | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								assets/arrpc.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								assets/arrpc.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								assets/arrpc_bridge_mod.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								assets/arrpc_bridge_mod.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | 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); | ||||||
| @@ -139,10 +139,12 @@ setTimeout(() => { | |||||||
|  |  | ||||||
| function main() { | function main() { | ||||||
|   userscript.muteToggled.connect(() => { |   userscript.muteToggled.connect(() => { | ||||||
|  |     console.log("Toggling mute"); | ||||||
|     muteBtn && muteBtn.click(); |     muteBtn && muteBtn.click(); | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   userscript.deafenToggled.connect(() => { |   userscript.deafenToggled.connect(() => { | ||||||
|  |     console.log("Toggling deafen"); | ||||||
|     deafenBtn && deafenBtn.click(); |     deafenBtn && deafenBtn.click(); | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
| @@ -157,6 +159,25 @@ function main() { | |||||||
|     streamStartBtnClone.remove(); |     streamStartBtnClone.remove(); | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|  |   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 () => { |   setInterval(async () => { | ||||||
|     const streamActive = |     const streamActive = | ||||||
|       document.getElementsByClassName("panel-2ZFCRb activityPanel-9icbyU") |       document.getElementsByClassName("panel-2ZFCRb activityPanel-9icbyU") | ||||||
| @@ -266,15 +287,11 @@ function main() { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     muteBtn = buttonContainer |     muteBtn = buttonContainer | ||||||
|       ? buttonContainer.getElementsByClassName( |       ? buttonContainer.getElementsByTagName("button")[0] | ||||||
|           "button-12Fmur enabled-9OeuTA button-f2h6uQ lookBlank-21BCro colorBrand-I6CyqQ grow-2sR_-F" |  | ||||||
|         )[0] |  | ||||||
|       : null; |       : null; | ||||||
|  |  | ||||||
|     deafenBtn = buttonContainer |     deafenBtn = buttonContainer | ||||||
|       ? buttonContainer.getElementsByClassName( |       ? buttonContainer.getElementsByTagName("button")[1] | ||||||
|           "button-12Fmur enabled-9OeuTA button-f2h6uQ lookBlank-21BCro colorBrand-I6CyqQ grow-2sR_-F" |  | ||||||
|         )[1] |  | ||||||
|       : null; |       : null; | ||||||
|  |  | ||||||
|     if (resolutionString) { |     if (resolutionString) { | ||||||
| @@ -311,6 +328,18 @@ function main() { | |||||||
|             }) |             }) | ||||||
|           ); |           ); | ||||||
|  |  | ||||||
|  |           // section.appendChild( | ||||||
|  |           //   createButton("Install Theme", () => { | ||||||
|  |           //     userscript.showThemeDialog(); | ||||||
|  |           //   }) | ||||||
|  |           // ); | ||||||
|  |  | ||||||
|  |           // section.appendChild( | ||||||
|  |           //   createButton("Uninstall Theme", () => { | ||||||
|  |           //     userscript.installUserStyles(""); | ||||||
|  |           //   }) | ||||||
|  |           // ); | ||||||
|  |  | ||||||
|           section.appendChild( |           section.appendChild( | ||||||
|             createSwitch( |             createSwitch( | ||||||
|               "Move discord-screenaudio to the system tray instead of closing", |               "Move discord-screenaudio to the system tray instead of closing", | ||||||
|   | |||||||
| @@ -1,43 +0,0 @@ | |||||||
| let webclass; |  | ||||||
|  |  | ||||||
| const promise = new Promise((resolve) => { |  | ||||||
|   setTimeout(() => { |  | ||||||
|     new QWebChannel(qt.webChannelTransport, function (channel) { |  | ||||||
|       webclass = channel.objects.webclass; |  | ||||||
|       resolve(); |  | ||||||
|     }); |  | ||||||
|   }); |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| async function prepareWebclass() { |  | ||||||
|   if (!webclass) await promise; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| window.VencordNative = { |  | ||||||
|   getVersions: () => ({}), |  | ||||||
|   ipc: { |  | ||||||
|     send: async (event: string, ...args: any[]) => { |  | ||||||
|       await prepareWebclass(); |  | ||||||
|       webclass.vencordSend(event, args); |  | ||||||
|     }, |  | ||||||
|     sendSync: (event: string, ...args: any[]) => { |  | ||||||
|       // We need this hack because Vencord requires its settings right when it starts |  | ||||||
|       if (event === "VencordGetSettings") { |  | ||||||
|         return window.discordScreenaudioVencordSettings || "{}"; |  | ||||||
|       } else throw new Error("Synchroneous IPC not implemented"); |  | ||||||
|     }, |  | ||||||
|     on(event: string, listener: () => {}) { |  | ||||||
|       // TODO quickCss |  | ||||||
|     }, |  | ||||||
|     off(event: string, listener: () => {}) { |  | ||||||
|       // not used for now |  | ||||||
|     }, |  | ||||||
|     invoke: async (event: string, ...args: any[]) => { |  | ||||||
|       await prepareWebclass(); |  | ||||||
|       if (event === "VencordSetSettings") { |  | ||||||
|         window.discordScreenaudioVencordSettings = args[0]; |  | ||||||
|       } |  | ||||||
|       return webclass.vencordSend(event, args); |  | ||||||
|     }, |  | ||||||
|   }, |  | ||||||
| }; |  | ||||||
| @@ -1,14 +0,0 @@ | |||||||
| import definePlugin from "../utils/types"; |  | ||||||
|  |  | ||||||
| export default definePlugin({ |  | ||||||
|   name: "discord-screenaudio", |  | ||||||
|   authors: [ |  | ||||||
|     { |  | ||||||
|       name: "maltejur", |  | ||||||
|       id: 205966226709676032n, |  | ||||||
|     }, |  | ||||||
|   ], |  | ||||||
|   required: true, |  | ||||||
|   description: "UI patches for discord-screenaudio.", |  | ||||||
|   patches: [], |  | ||||||
| }); |  | ||||||
| @@ -1,15 +0,0 @@ | |||||||
| --- a/src/components/VencordSettings/VencordTab.tsx |  | ||||||
| +++ b/src/components/VencordSettings/VencordTab.tsx |  | ||||||
| @@ -87,10 +87,10 @@ function VencordSettings() { |  | ||||||
|                  <Card className={cl("quick-actions-card")}> |  | ||||||
|                      {IS_WEB ? ( |  | ||||||
|                          <Button |  | ||||||
| -                            onClick={() => require("../Monaco").launchMonacoEditor()} |  | ||||||
| +                            onClick={() => VencordNative.ipc.send(IpcEvents.OPEN_EXTERNAL, settingsDir)} |  | ||||||
|                              size={Button.Sizes.SMALL} |  | ||||||
|                              disabled={settingsDir === "Loading..."}> |  | ||||||
| -                            Open QuickCSS File |  | ||||||
| +                            Launch Directory |  | ||||||
|                          </Button> |  | ||||||
|                      ) : ( |  | ||||||
|                          <React.Fragment> |  | ||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -2,7 +2,8 @@ | |||||||
| <RCC> | <RCC> | ||||||
|   <qresource> |   <qresource> | ||||||
|     <file>assets/userscript.js</file> |     <file>assets/userscript.js</file> | ||||||
|     <file>assets/vencord/vencord.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> | ||||||
							
								
								
									
										36
									
								
								scripts/build_arrpc.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										36
									
								
								scripts/build_arrpc.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | #!/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,39 +0,0 @@ | |||||||
| #!/usr/bin/bash |  | ||||||
| set -e |  | ||||||
|  |  | ||||||
| cd "$(dirname "$0")/../submodules" |  | ||||||
|  |  | ||||||
| echo_status() { |  | ||||||
|   echo |  | ||||||
|   echo |  | ||||||
|   echo "-> $1..." |  | ||||||
| } |  | ||||||
|  |  | ||||||
| if [ ! -d "Vencord" ]; then |  | ||||||
|   echo_status "Cloning Vencord" |  | ||||||
|   git clone https://github.com/Vendicated/Vencord.git |  | ||||||
|   cd Vencord |  | ||||||
| else |  | ||||||
|   echo_status "Fetching Vencord changes" |  | ||||||
|   cd Vencord |  | ||||||
|   git fetch |  | ||||||
| fi |  | ||||||
|  |  | ||||||
| echo_status "Checking out latest commit" |  | ||||||
| git reset --hard HEAD |  | ||||||
| git checkout main |  | ||||||
| git reset --hard devbuild |  | ||||||
|  |  | ||||||
| echo_status "Installing dependencies" |  | ||||||
| pnpm i |  | ||||||
|  |  | ||||||
| echo_status "Patching Vencord" |  | ||||||
| cp -v ../../assets/vencord/plugin.js ./src/plugins/discord-screenaudio.js |  | ||||||
| cp -v ../../assets/vencord/VencordNativeStub.ts ./browser/VencordNativeStub.ts |  | ||||||
| patch -p1 -i ../../assets/vencord/settings.patch |  | ||||||
|  |  | ||||||
| echo_status "Building Vencord" |  | ||||||
| pnpm run buildWeb |  | ||||||
|  |  | ||||||
| echo_status "Copying built file" |  | ||||||
| cp -v ./dist/browser.js ../../assets/vencord/vencord.js |  | ||||||
							
								
								
									
										84
									
								
								src/centralwidget.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								src/centralwidget.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | |||||||
|  | #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; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										29
									
								
								src/centralwidget.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/centralwidget.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | |||||||
|  | #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); | ||||||
|  | }; | ||||||
| @@ -9,6 +9,7 @@ | |||||||
| #include <QFileInfo> | #include <QFileInfo> | ||||||
| #include <QMessageBox> | #include <QMessageBox> | ||||||
| #include <QNetworkReply> | #include <QNetworkReply> | ||||||
|  | #include <QTemporaryFile> | ||||||
| #include <QTimer> | #include <QTimer> | ||||||
| #include <QWebChannel> | #include <QWebChannel> | ||||||
| #include <QWebEngineScript> | #include <QWebEngineScript> | ||||||
| @@ -16,10 +17,12 @@ | |||||||
| #include <QWebEngineSettings> | #include <QWebEngineSettings> | ||||||
|  |  | ||||||
| DiscordPage::DiscordPage(QWidget *parent) : QWebEnginePage(parent) { | DiscordPage::DiscordPage(QWidget *parent) : QWebEnginePage(parent) { | ||||||
|   setBackgroundColor(QColor("#202225")); |   setBackgroundColor(QColor("#313338")); | ||||||
|  |  | ||||||
|   connect(this, &QWebEnginePage::featurePermissionRequested, this, |   connect(this, &QWebEnginePage::featurePermissionRequested, this, | ||||||
|           &DiscordPage::featurePermissionRequested); |           &DiscordPage::featurePermissionRequested); | ||||||
|  |   connect(this, &DiscordPage::fullScreenRequested, MainWindow::instance(), | ||||||
|  |           &MainWindow::fullScreenRequested); | ||||||
|  |  | ||||||
|   setupPermissions(); |   setupPermissions(); | ||||||
|  |  | ||||||
| @@ -34,20 +37,8 @@ DiscordPage::DiscordPage(QWidget *parent) : QWebEnginePage(parent) { | |||||||
|   injectFile(&DiscordPage::injectScript, "userscript.js", |   injectFile(&DiscordPage::injectScript, "userscript.js", | ||||||
|              ":/assets/userscript.js"); |              ":/assets/userscript.js"); | ||||||
|  |  | ||||||
|   injectFile(&DiscordPage::injectScript, "userscript.js", |  | ||||||
|              ":/assets/userscript.js"); |  | ||||||
|   QFile vencord(":/assets/vencord/vencord.js"); |  | ||||||
|   if (!vencord.open(QIODevice::ReadOnly)) |  | ||||||
|     qFatal("Failed to load vencord source with error: %s", |  | ||||||
|            vencord.errorString().toLatin1().constData()); |  | ||||||
|   injectScript( |  | ||||||
|       "vencord.js", |  | ||||||
|       QString("window.discordScreenaudioVencordSettings = `%1`; %2") |  | ||||||
|           .arg(m_userScript.vencordSend("VencordGetSettings", {}).toString(), |  | ||||||
|                vencord.readAll())); |  | ||||||
|   vencord.close(); |  | ||||||
|  |  | ||||||
|   setupUserStyles(); |   setupUserStyles(); | ||||||
|  |   setupArrpc(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void DiscordPage::setupPermissions() { | void DiscordPage::setupPermissions() { | ||||||
| @@ -65,13 +56,79 @@ void DiscordPage::setupPermissions() { | |||||||
| } | } | ||||||
|  |  | ||||||
| void DiscordPage::setupUserStyles() { | void DiscordPage::setupUserStyles() { | ||||||
|   QString file = |   qDebug(userstylesLog).noquote() | ||||||
|       QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + |       << "Looking for userstyles in" << m_configLocation.absolutePath(); | ||||||
|       "/userstyles.css"; |   m_userStylesFile = | ||||||
|   if (QFileInfo(file).exists()) { |       new QFile(m_configLocation.absoluteFilePath("userstyles.css")); | ||||||
|     qDebug(mainLog) << "Found userstyles:" << file; |   if (m_userStylesFile->exists()) { | ||||||
|     injectFile(&DiscordPage::injectStylesheet, "userstyles.js", file); |     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( | void DiscordPage::injectScript( | ||||||
| @@ -96,7 +153,6 @@ void DiscordPage::injectScript(QString name, QString content) { | |||||||
|  |  | ||||||
| void DiscordPage::injectStylesheet(QString name, QString content) { | void DiscordPage::injectStylesheet(QString name, QString content) { | ||||||
|   auto script = QString(R"(const stylesheet = document.createElement("style"); |   auto script = QString(R"(const stylesheet = document.createElement("style"); | ||||||
| stylesheet.type = "text/css"; |  | ||||||
| stylesheet.id = "%1"; | stylesheet.id = "%1"; | ||||||
| stylesheet.innerText = `%2`; | stylesheet.innerText = `%2`; | ||||||
| document.head.appendChild(stylesheet); | document.head.appendChild(stylesheet); | ||||||
| @@ -205,11 +261,15 @@ 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"); |   auto colorSegments = message.split("%c"); | ||||||
|  |   if (colorSegments[0] != "") { | ||||||
|  |     for (auto line : colorSegments[0].split("\n")) | ||||||
|  |       qDebug(discordLog) << line.toUtf8().constData(); | ||||||
|  |   } | ||||||
|   for (auto segment : colorSegments.mid(1)) { |   for (auto segment : colorSegments.mid(1)) { | ||||||
|     auto lines = segment.split("\n"); |     auto lines = segment.split("\n"); | ||||||
|     QString ansi; |     QString ansi; | ||||||
|     uint endOfStyles = lines.length(); |     uint endOfStyles = lines.length(); | ||||||
|     for (size_t line = 1; line < lines.length(); line++) { |     for (auto line = 1; line < lines.length(); line++) { | ||||||
|       if (!lines[line].endsWith(";")) { |       if (!lines[line].endsWith(";")) { | ||||||
|         endOfStyles = line; |         endOfStyles = line; | ||||||
|         break; |         break; | ||||||
| @@ -223,9 +283,7 @@ void DiscordPage::javaScriptConsoleMessage( | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     qDebug(discordLog) << (ansi + lines[0].trimmed() + "\033[0m " + |     qDebug(discordLog) << (ansi + lines[0].trimmed() + "\033[0m " + | ||||||
|                            ((lines.length() > endOfStyles) |                            lines[endOfStyles].trimmed()) | ||||||
|                                 ? lines[endOfStyles].trimmed() |  | ||||||
|                                 : "")) |  | ||||||
|                               .toUtf8() |                               .toUtf8() | ||||||
|                               .constData(); |                               .constData(); | ||||||
|     for (auto line : lines.mid(endOfStyles + 1)) { |     for (auto line : lines.mid(endOfStyles + 1)) { | ||||||
| @@ -233,3 +291,21 @@ void DiscordPage::javaScriptConsoleMessage( | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | UserScript *DiscordPage::userScript() { return &m_userScript; } | ||||||
|  |  | ||||||
|  | void DiscordPage::setupArrpc() { | ||||||
|  |   QFile nodejs("/usr/bin/node"); | ||||||
|  |   if (nodejs.exists()) { | ||||||
|  |     auto arrpcSource = QTemporaryFile::createNativeFile(":/assets/arrpc.js"); | ||||||
|  |     qDebug(mainLog).noquote() | ||||||
|  |         << "NodeJS found, starting arRPC located at" << arrpcSource->fileName(); | ||||||
|  |     m_arrpcProcess.setProcessChannelMode(QProcess::ForwardedChannels); | ||||||
|  |     m_arrpcProcess.setProgram(nodejs.fileName()); | ||||||
|  |     m_arrpcProcess.setArguments(QStringList{arrpcSource->fileName()}); | ||||||
|  |     m_arrpcProcess.start(); | ||||||
|  |  | ||||||
|  |     injectFile(&DiscordPage::injectScript, "arrpc_bridge_mod.js", | ||||||
|  |                ":/assets/arrpc_bridge_mod.js"); | ||||||
|  |   } | ||||||
|  | } | ||||||
|   | |||||||
| @@ -1,9 +1,12 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include "streamdialog.h" |  | ||||||
| #include "userscript.h" | #include "userscript.h" | ||||||
| #include "virtmic.h" |  | ||||||
|  |  | ||||||
|  | #include <QDir> | ||||||
|  | #include <QFile> | ||||||
|  | #include <QNetworkAccessManager> | ||||||
|  | #include <QProcess> | ||||||
|  | #include <QStandardPaths> | ||||||
| #include <QWebEngineFullScreenRequest> | #include <QWebEngineFullScreenRequest> | ||||||
| #include <QWebEnginePage> | #include <QWebEnginePage> | ||||||
| #include <QWebEngineScript> | #include <QWebEngineScript> | ||||||
| @@ -13,11 +16,20 @@ class DiscordPage : public QWebEnginePage { | |||||||
|  |  | ||||||
| public: | public: | ||||||
|   explicit DiscordPage(QWidget *parent = nullptr); |   explicit DiscordPage(QWidget *parent = nullptr); | ||||||
|  |   UserScript *userScript(); | ||||||
|  |  | ||||||
| private: | private: | ||||||
|   UserScript m_userScript; |   UserScript m_userScript; | ||||||
|  |   QFile *m_userStylesFile; | ||||||
|  |   QString m_userStylesContent; | ||||||
|  |   QNetworkAccessManager m_networkAccessManager; | ||||||
|  |   QProcess m_arrpcProcess; | ||||||
|  |   const QDir m_configLocation = | ||||||
|  |       QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); | ||||||
|   void setupPermissions(); |   void setupPermissions(); | ||||||
|   void setupUserStyles(); |   void setupUserStyles(); | ||||||
|  |   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; | ||||||
| @@ -36,6 +48,9 @@ private: | |||||||
| private Q_SLOTS: | private Q_SLOTS: | ||||||
|   void featurePermissionRequested(const QUrl &securityOrigin, |   void featurePermissionRequested(const QUrl &securityOrigin, | ||||||
|                                   QWebEnginePage::Feature feature); |                                   QWebEnginePage::Feature feature); | ||||||
|  |  | ||||||
|  | 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 | ||||||
|   | |||||||
| @@ -5,3 +5,4 @@ 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,3 +7,4 @@ 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); | ||||||
|   | |||||||
| @@ -24,9 +24,6 @@ int main(int argc, char *argv[]) { | |||||||
|       "Custom Discord client with the ability to stream audio on Linux"); |       "Custom Discord client with the ability to stream audio on Linux"); | ||||||
|   parser.addHelpOption(); |   parser.addHelpOption(); | ||||||
|   parser.addVersionOption(); |   parser.addVersionOption(); | ||||||
|   QCommandLineOption virtmicOption("virtmic", "Start the Virtual Microphone", |  | ||||||
|                                    "target"); |  | ||||||
|   parser.addOption(virtmicOption); |  | ||||||
|   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); | ||||||
| @@ -36,10 +33,6 @@ int main(int argc, char *argv[]) { | |||||||
|  |  | ||||||
|   parser.process(app); |   parser.process(app); | ||||||
|  |  | ||||||
|   if (parser.isSet(virtmicOption)) { |  | ||||||
|     Virtmic::start(parser.value(virtmicOption)); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   qputenv("QTWEBENGINE_CHROMIUM_FLAGS", |   qputenv("QTWEBENGINE_CHROMIUM_FLAGS", | ||||||
|           "--enable-features=WebRTCPipeWireCapturer " + |           "--enable-features=WebRTCPipeWireCapturer " + | ||||||
|               qgetenv("QTWEBENGINE_CHROMIUM_FLAGS")); |               qgetenv("QTWEBENGINE_CHROMIUM_FLAGS")); | ||||||
|   | |||||||
| @@ -16,11 +16,7 @@ | |||||||
| #include <QThread> | #include <QThread> | ||||||
| #include <QTimer> | #include <QTimer> | ||||||
| #include <QUrl> | #include <QUrl> | ||||||
| #include <QWebEngineNotification> | #include <QWebEngineFullScreenRequest> | ||||||
| #include <QWebEngineProfile> |  | ||||||
| #include <QWebEngineScript> |  | ||||||
| #include <QWebEngineScriptCollection> |  | ||||||
| #include <QWebEngineSettings> |  | ||||||
| #include <QWidget> | #include <QWidget> | ||||||
|  |  | ||||||
| MainWindow *MainWindow::m_instance = nullptr; | MainWindow *MainWindow::m_instance = nullptr; | ||||||
| @@ -29,12 +25,17 @@ MainWindow::MainWindow(bool useNotifySend, QWidget *parent) | |||||||
|     : QMainWindow(parent) { |     : QMainWindow(parent) { | ||||||
|   assert(MainWindow::m_instance == nullptr); |   assert(MainWindow::m_instance == nullptr); | ||||||
|   MainWindow::m_instance = this; |   MainWindow::m_instance = this; | ||||||
|   m_useNotifySend = useNotifySend; |  | ||||||
|   setupSettings(); |   setupSettings(); | ||||||
|   setupWebView(); |   m_settings->setValue("useNotifySend", useNotifySend); | ||||||
|  |   m_centralWidget = new CentralWidget(this); | ||||||
|  |   setCentralWidget(m_centralWidget); | ||||||
|   setupTrayIcon(); |   setupTrayIcon(); | ||||||
|  |   if (m_settings->contains("geometry")) { | ||||||
|  |     restoreGeometry(m_settings->value("geometry").toByteArray()); | ||||||
|  |   } else { | ||||||
|     resize(1000, 700); |     resize(1000, 700); | ||||||
|     showMaximized(); |     showMaximized(); | ||||||
|  |   } | ||||||
|   if (m_settings->value("trayIcon", false).toBool() && |   if (m_settings->value("trayIcon", false).toBool() && | ||||||
|       m_settings->value("startHidden", false).toBool()) { |       m_settings->value("startHidden", false).toBool()) { | ||||||
|     hide(); |     hide(); | ||||||
| @@ -42,49 +43,6 @@ MainWindow::MainWindow(bool useNotifySend, QWidget *parent) | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void MainWindow::setupWebView() { |  | ||||||
|   auto page = new DiscordPage(this); |  | ||||||
|   connect(page, &QWebEnginePage::fullScreenRequested, this, |  | ||||||
|           &MainWindow::fullScreenRequested); |  | ||||||
|  |  | ||||||
|   m_webView = new QWebEngineView(this); |  | ||||||
|   m_webView->setPage(page); |  | ||||||
|  |  | ||||||
|   if (m_useKF5Notifications || m_useNotifySend) |  | ||||||
|     QWebEngineProfile::defaultProfile()->setNotificationPresenter( |  | ||||||
|         [&](std::unique_ptr<QWebEngineNotification> notificationInfo) { |  | ||||||
|           if (m_useNotifySend) { |  | ||||||
|             auto title = notificationInfo->title(); |  | ||||||
|             auto message = notificationInfo->message(); |  | ||||||
|             auto image_path = |  | ||||||
|                 QString("/tmp/discord-screenaudio-%1.png").arg(title); |  | ||||||
|             notificationInfo->icon().save(image_path); |  | ||||||
|             QProcess::execute("notify-send", |  | ||||||
|                               {"--icon", image_path, "--app-name", |  | ||||||
|                                "discord-screenaudio", title, message}); |  | ||||||
|           } else if (m_useKF5Notifications) { |  | ||||||
| #ifdef KNOTIFICATIONS |  | ||||||
|             KNotification *notification = |  | ||||||
|                 new KNotification("discordNotification"); |  | ||||||
|             notification->setTitle(notificationInfo->title()); |  | ||||||
|             notification->setText(notificationInfo->message()); |  | ||||||
|             notification->setPixmap( |  | ||||||
|                 QPixmap::fromImage(notificationInfo->icon())); |  | ||||||
|             notification->setDefaultAction("View"); |  | ||||||
|             connect(notification, &KNotification::defaultActivated, |  | ||||||
|                     [&, notificationInfo = std::move(notificationInfo)]() { |  | ||||||
|                       notificationInfo->click(); |  | ||||||
|                       show(); |  | ||||||
|                       activateWindow(); |  | ||||||
|                     }); |  | ||||||
|             notification->sendEvent(); |  | ||||||
| #endif |  | ||||||
|           } |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|   setCentralWidget(m_webView); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void MainWindow::fullScreenRequested( | void MainWindow::fullScreenRequested( | ||||||
|     QWebEngineFullScreenRequest fullScreenRequest) { |     QWebEngineFullScreenRequest fullScreenRequest) { | ||||||
|   fullScreenRequest.accept(); |   fullScreenRequest.accept(); | ||||||
| @@ -141,7 +99,8 @@ void MainWindow::cleanTrayIcon() { | |||||||
| } | } | ||||||
|  |  | ||||||
| void MainWindow::setupSettings() { | void MainWindow::setupSettings() { | ||||||
|   m_settings = new QSettings("maltejur", "discord-screenaudio", this); |   m_settings = | ||||||
|  |       new QSettings("discord-screenaudio", "discord-screenaudio", this); | ||||||
|   m_settings->beginGroup("settings"); |   m_settings->beginGroup("settings"); | ||||||
|   m_settings->endGroup(); |   m_settings->endGroup(); | ||||||
| } | } | ||||||
| @@ -160,8 +119,14 @@ void MainWindow::setTrayIcon(bool enabled) { | |||||||
| void MainWindow::closeEvent(QCloseEvent *event) { | void MainWindow::closeEvent(QCloseEvent *event) { | ||||||
|   if (m_settings->value("trayIcon", false).toBool()) { |   if (m_settings->value("trayIcon", false).toBool()) { | ||||||
|     hide(); |     hide(); | ||||||
|   } else |   } else { | ||||||
|  |     m_settings->setValue("geometry", saveGeometry()); | ||||||
|     QApplication::quit(); |     QApplication::quit(); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| MainWindow *MainWindow::instance() { return m_instance; } | MainWindow *MainWindow::instance() { return m_instance; } | ||||||
|  |  | ||||||
|  | CentralWidget *MainWindow::centralWidget() { | ||||||
|  |   return instance()->m_centralWidget; | ||||||
|  | }; | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include "discordpage.h" | #include "centralwidget.h" | ||||||
|  | #include "virtmic.h" | ||||||
|  |  | ||||||
| #include <QMainWindow> | #include <QMainWindow> | ||||||
| #include <QMenu> | #include <QMenu> | ||||||
| @@ -8,10 +9,8 @@ | |||||||
| #include <QSettings> | #include <QSettings> | ||||||
| #include <QString> | #include <QString> | ||||||
| #include <QSystemTrayIcon> | #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 | ||||||
| @@ -20,31 +19,22 @@ public: | |||||||
|   explicit MainWindow(bool useNotifySend = false, QWidget *parent = nullptr); |   explicit MainWindow(bool useNotifySend = false, QWidget *parent = nullptr); | ||||||
|   static MainWindow *instance(); |   static MainWindow *instance(); | ||||||
|   QSettings *settings() const; |   QSettings *settings() const; | ||||||
|  |   static CentralWidget *centralWidget(); | ||||||
|  |  | ||||||
| private: | private: | ||||||
|   void setupWebView(); |  | ||||||
|   void setupTrayIcon(); |   void setupTrayIcon(); | ||||||
|   void cleanTrayIcon(); |   void cleanTrayIcon(); | ||||||
|   void setupSettings(); |   void setupSettings(); | ||||||
|   QWebEngineView *m_webView; |  | ||||||
|   QWebEngineProfile *prepareProfile(); |   QWebEngineProfile *prepareProfile(); | ||||||
|   DiscordPage *m_discordPage; |  | ||||||
|   void closeEvent(QCloseEvent *event) override; |   void closeEvent(QCloseEvent *event) override; | ||||||
|   QSystemTrayIcon *m_trayIcon = nullptr; |   QSystemTrayIcon *m_trayIcon = nullptr; | ||||||
|   QMenu *m_trayIconMenu; |   QMenu *m_trayIconMenu; | ||||||
|   QSettings *m_settings; |   QSettings *m_settings; | ||||||
|   bool m_wasMaximized; |   bool m_wasMaximized; | ||||||
|   static MainWindow *m_instance; |   static MainWindow *m_instance; | ||||||
|   bool m_useNotifySend; |   CentralWidget *m_centralWidget; | ||||||
| #ifdef KNOTIFICATIONS |  | ||||||
|   bool m_useKF5Notifications = true; |  | ||||||
| #else |  | ||||||
|   bool m_useKF5Notifications = false; |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| public Q_SLOTS: | public Q_SLOTS: | ||||||
|   void setTrayIcon(bool enabled); |   void setTrayIcon(bool enabled); | ||||||
|  |  | ||||||
| private Q_SLOTS: |  | ||||||
|   void fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest); |   void fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| #include "streamdialog.h" | #include "streamdialog.h" | ||||||
|  | #include "mainwindow.h" | ||||||
| #include "virtmic.h" | #include "virtmic.h" | ||||||
|  |  | ||||||
| #include <QComboBox> | #include <QComboBox> | ||||||
|   | |||||||
| @@ -4,9 +4,9 @@ | |||||||
|  |  | ||||||
| #include <QApplication> | #include <QApplication> | ||||||
| #include <QDebug> | #include <QDebug> | ||||||
| #include <QDesktopServices> |  | ||||||
| #include <QDir> |  | ||||||
| #include <QFile> | #include <QFile> | ||||||
|  | #include <QInputDialog> | ||||||
|  | #include <QMessageBox> | ||||||
| #include <QStandardPaths> | #include <QStandardPaths> | ||||||
| #include <QTimer> | #include <QTimer> | ||||||
|  |  | ||||||
| @@ -42,8 +42,12 @@ void UserScript::setupHelpMenu() { | |||||||
|   aboutData.addCredit( |   aboutData.addCredit( | ||||||
|       "Curve", "For creating the Rohrkabel library used in this project.", |       "Curve", "For creating the Rohrkabel library used in this project.", | ||||||
|       QString(), "https://github.com/Curve"); |       QString(), "https://github.com/Curve"); | ||||||
|   aboutData.addComponent("Rohrkabel", "A C++ RAII Pipewire-API Wrapper", "1.3", |   aboutData.addComponent("Rohrkabel", "A C++ RAII Pipewire-API Wrapper", "1.5", | ||||||
|                          "https://github.com/Soundux/rohrkabel"); |                          "https://github.com/Soundux/rohrkabel"); | ||||||
|  |   aboutData.addComponent( | ||||||
|  |       "Soundux/channel ", | ||||||
|  |       "A C++ implementation of Rust's std::sync::mpsc::channel", nullptr, | ||||||
|  |       "https://github.com/Soundux/channel"); | ||||||
|   m_helpMenu = new KHelpMenu(MainWindow::instance(), aboutData); |   m_helpMenu = new KHelpMenu(MainWindow::instance(), aboutData); | ||||||
| #endif | #endif | ||||||
| } | } | ||||||
| @@ -62,7 +66,7 @@ void UserScript::setupShortcutsDialog() { | |||||||
|   auto toggleDeafenAction = new QAction(this); |   auto toggleDeafenAction = new QAction(this); | ||||||
|   toggleDeafenAction->setText("Toggle Deafen"); |   toggleDeafenAction->setText("Toggle Deafen"); | ||||||
|   toggleDeafenAction->setIcon(QIcon::fromTheme("audio-volume-muted")); |   toggleDeafenAction->setIcon(QIcon::fromTheme("audio-volume-muted")); | ||||||
|   connect(toggleMuteAction, &QAction::triggered, this, |   connect(toggleDeafenAction, &QAction::triggered, this, | ||||||
|           &UserScript::deafenToggled); |           &UserScript::deafenToggled); | ||||||
|  |  | ||||||
|   m_actionCollection = new KActionCollection(this); |   m_actionCollection = new KActionCollection(this); | ||||||
| @@ -168,67 +172,13 @@ void UserScript::showStreamDialog() { | |||||||
|   m_streamDialog->updateTargets(); |   m_streamDialog->updateTargets(); | ||||||
| } | } | ||||||
|  |  | ||||||
| QVariant UserScript::vencordSend(QString event, QVariantList args) { | void UserScript::showThemeDialog() { | ||||||
|   QString configFolder = |   auto url = QInputDialog::getText(MainWindow::instance(), "Theme Installation", | ||||||
|       QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + |                                    "Please enter the URL of the Theme"); | ||||||
|       "/vencord"; |   if (url != "") | ||||||
|   QString quickCssFile = configFolder + "/quickCss.css"; |     emit shouldInstallUserStyles(url); | ||||||
|   QString settingsFile = configFolder + "/settings.json"; | } | ||||||
|  |  | ||||||
|   if (!QDir().exists(configFolder)) | void UserScript::installUserStyles(QString url) { | ||||||
|     QDir().mkpath(configFolder); |   emit shouldInstallUserStyles(url); | ||||||
|  |  | ||||||
|   if (event == "VencordGetRepo") { |  | ||||||
|     return true; |  | ||||||
|   } |  | ||||||
|   if (event == "VencordGetSettingsDir") { |  | ||||||
|     return configFolder; |  | ||||||
|   } |  | ||||||
|   if (event == "VencordGetQuickCss") { |  | ||||||
|     if (QFile::exists(quickCssFile)) { |  | ||||||
|       QFile file(quickCssFile); |  | ||||||
|       if (!file.open(QIODevice::ReadOnly)) |  | ||||||
|         qFatal("Failed to load %s with error: %s", |  | ||||||
|                quickCssFile.toLatin1().constData(), |  | ||||||
|                file.errorString().toLatin1().constData()); |  | ||||||
|       auto content = file.readAll(); |  | ||||||
|       file.close(); |  | ||||||
|       return QString(content); |  | ||||||
|     } else |  | ||||||
|       return ""; |  | ||||||
|   } |  | ||||||
|   if (event == "VencordGetSettings") { |  | ||||||
|     if (QFile::exists(settingsFile)) { |  | ||||||
|       QFile file(settingsFile); |  | ||||||
|       if (!file.open(QIODevice::ReadOnly)) |  | ||||||
|         qFatal("Failed to load %s with error: %s", |  | ||||||
|                settingsFile.toLatin1().constData(), |  | ||||||
|                file.errorString().toLatin1().constData()); |  | ||||||
|       auto content = file.readAll(); |  | ||||||
|       file.close(); |  | ||||||
|       return QString(content); |  | ||||||
|     } else |  | ||||||
|       return "{}"; |  | ||||||
|   } |  | ||||||
|   if (event == "VencordSetSettings") { |  | ||||||
|     QFile file(settingsFile); |  | ||||||
|     if (!file.open(QIODevice::WriteOnly)) |  | ||||||
|       qFatal("Failed to load %s with error: %s", |  | ||||||
|              settingsFile.toLatin1().constData(), |  | ||||||
|              file.errorString().toLatin1().constData()); |  | ||||||
|     file.write(args[0].toString().toUtf8()); |  | ||||||
|     file.close(); |  | ||||||
|     return true; |  | ||||||
|   } |  | ||||||
|   if (event == "VencordGetUpdates") { |  | ||||||
|     return QVariantMap{{"ok", true}, {"value", QVariantList()}}; |  | ||||||
|   } |  | ||||||
|   if (event == "VencordOpenExternal") { |  | ||||||
|     QDesktopServices::openUrl(QUrl(args[0].toString())); |  | ||||||
|     return true; |  | ||||||
|   } |  | ||||||
|   if (event == "VencordOpenQuickCss") { |  | ||||||
|     return true; |  | ||||||
|   } |  | ||||||
|   assert(false); |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -18,6 +18,10 @@ | |||||||
|  |  | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | #ifdef KNOTIFICATIONS | ||||||
|  | #include <KNotification> | ||||||
|  | #endif | ||||||
|  |  | ||||||
| class UserScript : public QObject { | class UserScript : public QObject { | ||||||
|   Q_OBJECT |   Q_OBJECT | ||||||
|  |  | ||||||
| @@ -27,12 +31,17 @@ public: | |||||||
|   Q_PROPERTY(QString version READ version CONSTANT); |   Q_PROPERTY(QString version READ version CONSTANT); | ||||||
|   Q_PROPERTY(bool kxmlgui MEMBER m_kxmlgui CONSTANT); |   Q_PROPERTY(bool kxmlgui MEMBER m_kxmlgui CONSTANT); | ||||||
|   Q_PROPERTY(bool kglobalaccel MEMBER m_kglobalaccel 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: | private: | ||||||
|   QProcess m_virtmicProcess; |   QProcess m_virtmicProcess; | ||||||
|   StreamDialog *m_streamDialog; |   StreamDialog *m_streamDialog; | ||||||
|   bool m_kxmlgui = false; |   bool m_kxmlgui = false; | ||||||
|   bool m_kglobalaccel = false; |   bool m_kglobalaccel = false; | ||||||
|  |   QString m_userstyles; | ||||||
|  |   QString m_loadingMessage; | ||||||
| #ifdef KXMLGUI | #ifdef KXMLGUI | ||||||
|   KHelpMenu *m_helpMenu; |   KHelpMenu *m_helpMenu; | ||||||
| #ifdef KGLOBALACCEL | #ifdef KGLOBALACCEL | ||||||
| @@ -40,7 +49,6 @@ private: | |||||||
|   KShortcutsDialog *m_shortcutsDialog; |   KShortcutsDialog *m_shortcutsDialog; | ||||||
| #endif | #endif | ||||||
| #endif | #endif | ||||||
|   QString m_vencordSettings; |  | ||||||
|   void setupHelpMenu(); |   void setupHelpMenu(); | ||||||
|   void setupShortcutsDialog(); |   void setupShortcutsDialog(); | ||||||
|   void setupStreamDialog(); |   void setupStreamDialog(); | ||||||
| @@ -50,6 +58,9 @@ Q_SIGNALS: | |||||||
|   void muteToggled(); |   void muteToggled(); | ||||||
|   void deafenToggled(); |   void deafenToggled(); | ||||||
|   void streamStarted(bool video, int width, int height, int frameRate); |   void streamStarted(bool video, int width, int height, int frameRate); | ||||||
|  |   void userstylesChanged(); | ||||||
|  |   void loadingMessageChanged(QString message); | ||||||
|  |   void shouldInstallUserStyles(QString url); | ||||||
|  |  | ||||||
| public Q_SLOTS: | public Q_SLOTS: | ||||||
|   void log(QString message); |   void log(QString message); | ||||||
| @@ -63,7 +74,8 @@ public Q_SLOTS: | |||||||
|   void showStreamDialog(); |   void showStreamDialog(); | ||||||
|   void stopVirtmic(); |   void stopVirtmic(); | ||||||
|   void startVirtmic(QString target); |   void startVirtmic(QString target); | ||||||
|   QVariant vencordSend(QString event, QVariantList args); |   void showThemeDialog(); | ||||||
|  |   void installUserStyles(QString url); | ||||||
|  |  | ||||||
| private Q_SLOTS: | private Q_SLOTS: | ||||||
|   void startStream(bool video, bool audio, int width, int height, int frameRate, |   void startStream(bool video, bool audio, int width, int height, int frameRate, | ||||||
|   | |||||||
							
								
								
									
										172
									
								
								src/virtmic.cpp
									
									
									
									
									
								
							
							
						
						
									
										172
									
								
								src/virtmic.cpp
									
									
									
									
									
								
							| @@ -1,95 +1,99 @@ | |||||||
| #include "virtmic.h" | #include "virtmic.h" | ||||||
| #include "log.h" | #include "log.h" | ||||||
|  |  | ||||||
| #include <rohrkabel/loop/main.hpp> | QThread virtmicThread; | ||||||
| #include <rohrkabel/registry/registry.hpp> | std::unique_ptr<pipewire::sender<Virtmic::set_target, Virtmic::terminate>> | ||||||
|  |     senderr; | ||||||
|  | std::unique_ptr<cr::receiver<Virtmic::new_targets>> receiverr; | ||||||
|  |  | ||||||
| namespace Virtmic { | void Virtmic::instance() { | ||||||
|  |   if (!virtmicThread.isRunning()) { | ||||||
| const QStringList EXCLUDE_TARGETS{"Chromium input", "discord-screenaudio"}; |     auto [main_sender, main_receiver] = cr::channel<new_targets>(); | ||||||
|  |     auto [pw_sender, pw_receiver] = pipewire::channel<set_target, terminate>(); | ||||||
| QVector<QString> getTargets() { |     Virtmic *virtmic = | ||||||
|   auto main_loop = pipewire::main_loop(); |         new Virtmic(std::move(pw_receiver), std::move(main_sender)); | ||||||
|   auto context = pipewire::context(main_loop); |     virtmic->moveToThread(&virtmicThread); | ||||||
|   auto core = pipewire::core(context); |     virtmicThread.start(); | ||||||
|   auto reg = pipewire::registry(core); |     QMetaObject::invokeMethod(virtmic, "run"); | ||||||
|  |     receiverr = std::make_unique<cr::receiver<Virtmic::new_targets>>( | ||||||
|   QVector<QString> targets; |         std::move(main_receiver)); | ||||||
|  |     senderr = std::make_unique< | ||||||
|   auto reg_listener = reg.listen<pipewire::registry_listener>(); |         pipewire::sender<Virtmic::set_target, Virtmic::terminate>>( | ||||||
|   reg_listener.on<pipewire::registry_event::global>( |         std::move(pw_sender)); | ||||||
|       [&](const pipewire::global &global) { |  | ||||||
|         if (global.type == pipewire::node::type) { |  | ||||||
|           auto node = reg.bind<pipewire::node>(global.id); |  | ||||||
|           auto info = node.info(); |  | ||||||
|           QString name; |  | ||||||
|           if (info.props.count("application.name") && |  | ||||||
|               info.props["application.name"] != "") |  | ||||||
|             name = QString::fromStdString(info.props["application.name"]); |  | ||||||
|           else |  | ||||||
|             name = QString::fromStdString( |  | ||||||
|                 info.props["application.process.binary"]); |  | ||||||
|  |  | ||||||
|           if (name != "" && !EXCLUDE_TARGETS.contains(name) && |  | ||||||
|               !targets.contains(name)) { |  | ||||||
|             targets.append(name); |  | ||||||
|   } |   } | ||||||
|         } |  | ||||||
|       }); |  | ||||||
|   core.update(); |  | ||||||
|  |  | ||||||
|   return targets; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void start(QString _target) { | void Virtmic::setTarget(QString target) { | ||||||
|   std::map<std::uint32_t, pipewire::port> ports; |   senderr.get()->send<Virtmic::set_target>({target}); | ||||||
|   std::unique_ptr<pipewire::port> virt_fl, virt_fr; | } | ||||||
|  |  | ||||||
|   std::map<std::uint32_t, pipewire::node_info> nodes; | void Virtmic::getTargets() { senderr.get()->send<Virtmic::get_targets>(); } | ||||||
|   std::map<std::uint32_t, pipewire::link_factory> links; |  | ||||||
|  |  | ||||||
|   auto main_loop = pipewire::main_loop(); | Virtmic::Virtmic(pipewire::receiver<set_target, terminate> receiver, | ||||||
|   auto context = pipewire::context(main_loop); |                  cr::sender<new_targets> sender) { | ||||||
|   auto core = pipewire::core(context); |   m_receiver = std::make_unique<pipewire::receiver<set_target, terminate>>( | ||||||
|   auto reg = pipewire::registry(core); |       std::move(receiver)); | ||||||
|  |   m_sender = std::make_unique<cr::sender<new_targets>>(std::move(sender)); | ||||||
|  |   virtual_mic = std::make_unique<pipewire::proxy>( | ||||||
|  |       std::move(*core.create("adapter", | ||||||
|  |                              {{"node.name", "discord-screenaudio-virtmic"}, | ||||||
|  |                               {"media.class", "Audio/Source/Virtual"}, | ||||||
|  |                               {"factory.name", "support.null-audio-sink"}, | ||||||
|  |                               {"audio.channels", "2"}, | ||||||
|  |                               {"audio.position", "FL,FR"}}, | ||||||
|  |                              pipewire::node::type, pipewire::node::version, | ||||||
|  |                              pipewire::update_strategy::none) | ||||||
|  |                      .get())); | ||||||
|  |   metadata_listener.on<pipewire::registry_event::global>( | ||||||
|  |       [&](const auto &global) { globalEvent(global); }); | ||||||
|  |   metadata_listener.on<pipewire::registry_event::global_removed>( | ||||||
|  |       [&](const std::uint32_t id) { globalRemovedEvent(id); }); | ||||||
|  | } | ||||||
|  |  | ||||||
|   auto link = [&](const std::string &target, pipewire::core &core) { | void Virtmic::run() { | ||||||
|  |   while (true) { | ||||||
|  |     main_loop.run(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Virtmic::link() { | ||||||
|   for (const auto &[port_id, port] : ports) { |   for (const auto &[port_id, port] : ports) { | ||||||
|  |     auto port_info = port.info(); | ||||||
|     if (!virt_fl || !virt_fr) |     if (!virt_fl || !virt_fr) | ||||||
|       continue; |       continue; | ||||||
|  |  | ||||||
|     if (links.count(port_id)) |     if (links.count(port_id)) | ||||||
|       continue; |       continue; | ||||||
|  |  | ||||||
|       if (port.info().direction == pipewire::port_direction::input) |     if (port_info.direction == pipewire::port_direction::input) | ||||||
|       continue; |       continue; | ||||||
|  |  | ||||||
|       if (!port.info().props.count("node.id")) |     if (!port_info.props.count("node.id")) | ||||||
|       continue; |       continue; | ||||||
|  |  | ||||||
|       auto parent_id = std::stoul(port.info().props["node.id"]); |     auto parent_id = std::stoul(port_info.props["node.id"]); | ||||||
|  |  | ||||||
|     if (!nodes.count(parent_id)) |     if (!nodes.count(parent_id)) | ||||||
|       continue; |       continue; | ||||||
|  |  | ||||||
|       auto &parent = nodes.at(parent_id); |     auto &parent = nodes[parent_id]; | ||||||
|       std::string name; |     QString name; | ||||||
|     if (parent.props.count("application.name") && |     if (parent.props.count("application.name") && | ||||||
|         parent.props["application.name"] != "") |         parent.props["application.name"] != "") | ||||||
|         name = parent.props["application.name"]; |       name = QString::fromStdString(parent.props["application.name"]); | ||||||
|     else |     else | ||||||
|         name = parent.props["application.process.binary"]; |       name = QString::fromStdString(parent.props["application.process.binary"]); | ||||||
|  |  | ||||||
|     if (name == target || |     if (name == target || | ||||||
|           (target == "[All Desktop Audio]" && |         (target == "[All Desktop Audio]" && !EXCLUDE_TARGETS.contains(name))) { | ||||||
|            !EXCLUDE_TARGETS.contains(QString::fromStdString(name)))) { |       auto fl = port_info.props["audio.channel"] == "FL"; | ||||||
|         auto fl = port.info().props["audio.channel"] == "FL"; |       links.emplace(port_id, | ||||||
|         links.emplace( |                     *core.create_simple<pipewire::link>(fl ? virt_fl->info().id | ||||||
|             port_id, |                                                            : virt_fr->info().id, | ||||||
|             core.create<pipewire::link_factory>( |                                                         port_id) | ||||||
|                 {fl ? virt_fl->info().id : virt_fr->info().id, port_id})); |                          .get()); | ||||||
|       qDebug(virtmicLog) << QString("Link: %1:%2 -> %3") |       qDebug(virtmicLog) << QString("Link: %1:%2 -> %3") | ||||||
|                                   .arg(QString::fromStdString(name)) |                                 .arg(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) | ||||||
| @@ -97,31 +101,13 @@ void start(QString _target) { | |||||||
|                                 .data(); |                                 .data(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   }; | } | ||||||
|  |  | ||||||
|   std::string target = _target.toLatin1().toStdString(); | void Virtmic::unlink() { links.clear(); } | ||||||
|  |  | ||||||
|   auto virtual_mic = core.create("adapter", | void Virtmic::globalEvent(const pipewire::global &global) { | ||||||
|                                  {{"node.name", "discord-screenaudio-virtmic"}, |  | ||||||
|                                   {"media.class", "Audio/Source/Virtual"}, |  | ||||||
|                                   {"factory.name", "support.null-audio-sink"}, |  | ||||||
|                                   {"audio.channels", "2"}, |  | ||||||
|                                   {"audio.position", "FL,FR"}}, |  | ||||||
|                                  pipewire::node::type, pipewire::node::version, |  | ||||||
|                                  pipewire::update_strategy::none); |  | ||||||
|  |  | ||||||
|   if (target == "[None]") { |  | ||||||
|     while (true) { |  | ||||||
|       main_loop.run(); |  | ||||||
|     } |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   auto reg_events = reg.listen<pipewire::registry_listener>(); |  | ||||||
|   reg_events.on<pipewire::registry_event::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).get(); | ||||||
|     auto info = node.info(); |     auto info = node.info(); | ||||||
|     std::string name; |     std::string name; | ||||||
|     if (info.props.count("application.name") && |     if (info.props.count("application.name") && | ||||||
| @@ -138,17 +124,17 @@ void start(QString _target) { | |||||||
|  |  | ||||||
|     if (!nodes.count(global.id)) { |     if (!nodes.count(global.id)) { | ||||||
|       nodes.emplace(global.id, node.info()); |       nodes.emplace(global.id, node.info()); | ||||||
|             link(target, core); |       link(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   if (global.type == pipewire::port::type) { |   if (global.type == pipewire::port::type) { | ||||||
|           auto port = reg.bind<pipewire::port>(global.id); |     auto port = *reg.bind<pipewire::port>(global.id).get(); | ||||||
|     auto info = port.info(); |     auto info = port.info(); | ||||||
|  |  | ||||||
|     if (info.props.count("node.id")) { |     if (info.props.count("node.id")) { | ||||||
|       auto node_id = std::stoul(info.props["node.id"]); |       auto node_id = std::stoul(info.props["node.id"]); | ||||||
|  |  | ||||||
|             if (node_id == virtual_mic.id() && |       if (node_id == virtual_mic.get()->id() && | ||||||
|           info.direction == pipewire::port_direction::input) { |           info.direction == pipewire::port_direction::input) { | ||||||
|         if (info.props["audio.channel"] == "FL") { |         if (info.props["audio.channel"] == "FL") { | ||||||
|           virt_fl = std::make_unique<pipewire::port>(std::move(port)); |           virt_fl = std::make_unique<pipewire::port>(std::move(port)); | ||||||
| @@ -159,13 +145,12 @@ void start(QString _target) { | |||||||
|         ports.emplace(global.id, std::move(port)); |         ports.emplace(global.id, std::move(port)); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|             link(target, core); |       link(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|       }); | } | ||||||
|  |  | ||||||
|   reg_events.on<pipewire::registry_event::global_removed>( | void Virtmic::globalRemovedEvent(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; |     std::string name; | ||||||
| @@ -186,11 +171,4 @@ void start(QString _target) { | |||||||
|   if (links.count(id)) { |   if (links.count(id)) { | ||||||
|     links.erase(id); |     links.erase(id); | ||||||
|   } |   } | ||||||
|       }); |  | ||||||
|  |  | ||||||
|   while (true) { |  | ||||||
|     main_loop.run(); |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| } // namespace Virtmic |  | ||||||
|   | |||||||
| @@ -1,12 +1,63 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include <QString> | #include <cr/channel.hpp> | ||||||
| #include <QVector> |  | ||||||
| #include <iostream> | #include <iostream> | ||||||
|  | #include <rohrkabel/channel/channel.hpp> | ||||||
|  | #include <rohrkabel/main_loop.hpp> | ||||||
|  | #include <rohrkabel/registry/registry.hpp> | ||||||
|  |  | ||||||
| namespace Virtmic { | #include <QMap> | ||||||
|  | #include <QScopedPointer> | ||||||
|  | #include <QStringList> | ||||||
|  | #include <QThread> | ||||||
|  |  | ||||||
| QVector<QString> getTargets(); | class Virtmic : public QObject { | ||||||
| void start(QString _target); |   Q_OBJECT | ||||||
|  | public: | ||||||
|  |   static void setTarget(QString target); | ||||||
|  |   static void getTargets(); | ||||||
|  |  | ||||||
| } // namespace Virtmic | public: | ||||||
|  |   struct set_target { | ||||||
|  |     QString name; | ||||||
|  |   }; | ||||||
|  |   struct get_targets {}; | ||||||
|  |   struct terminate {}; | ||||||
|  |   struct new_targets { | ||||||
|  |     QStringList targets; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | protected: | ||||||
|  |   static void instance(); | ||||||
|  |  | ||||||
|  | protected: | ||||||
|  |   Virtmic(pipewire::receiver<set_target, terminate> receiver, | ||||||
|  |           cr::sender<new_targets> sender); | ||||||
|  |   void run(); | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |   std::unique_ptr<pipewire::receiver<set_target, terminate>> m_receiver; | ||||||
|  |   std::unique_ptr<cr::sender<new_targets>> m_sender; | ||||||
|  |  | ||||||
|  |   const QStringList EXCLUDE_TARGETS{"Chromium input", "discord-screenaudio"}; | ||||||
|  |   QString target; | ||||||
|  |  | ||||||
|  |   pipewire::main_loop main_loop = pipewire::main_loop(); | ||||||
|  |   pipewire::context context = pipewire::context(main_loop); | ||||||
|  |   pipewire::core core = pipewire::core(context); | ||||||
|  |   pipewire::registry reg = pipewire::registry(core); | ||||||
|  |  | ||||||
|  |   pipewire::registry_listener metadata_listener = | ||||||
|  |       reg.listen<pipewire::registry_listener>(); | ||||||
|  |   std::unique_ptr<pipewire::proxy> virtual_mic; | ||||||
|  |  | ||||||
|  |   std::map<uint32_t, pipewire::port> ports; | ||||||
|  |   std::unique_ptr<pipewire::port> virt_fl, virt_fr; | ||||||
|  |   std::map<uint32_t, pipewire::node_info> nodes; | ||||||
|  |   std::map<uint32_t, pipewire::link> links; | ||||||
|  |  | ||||||
|  |   void link(); | ||||||
|  |   void unlink(); | ||||||
|  |   void globalEvent(const pipewire::global &global); | ||||||
|  |   void globalRemovedEvent(const std::uint32_t id); | ||||||
|  | }; | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								submodules/channel
									
									
									
									
									
										Submodule
									
								
							
							
								
								
								
								
								
							
						
						
									
										1
									
								
								submodules/channel
									
									
									
									
									
										Submodule
									
								
							 Submodule submodules/channel added at 6977815409
									
								
							 Submodule submodules/rohrkabel updated: 04bfb921c4...8a7705be07
									
								
							
		Reference in New Issue
	
	Block a user