Compare commits
	
		
			19 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | e3d87e8202 | ||
|  | 700d576ff8 | ||
|  | 02fdc62fbe | ||
|  | d9e3704176 | ||
|  | 36c8cfedb7 | ||
|  | c52daab420 | ||
|  | 8c4fae3410 | ||
|  | ac71e9bbcb | ||
|  | fc0dbb5b34 | ||
|  | 5df24629e6 | ||
|  | 1477a9d4c0 | ||
|  | 0b12487dfd | ||
|  | 906deee580 | ||
|  | 9d9e57df1e | ||
|  | 9eae8bbe4f | ||
|  | 0493a76117 | ||
|  | 100f9bf58e | ||
|  | 1eda9d75b0 | ||
|  | f750b76068 | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,2 +1,3 @@ | |||||||
| /build | /build | ||||||
| .vscode | .vscode | ||||||
|  | /submodules/Vencord | ||||||
|   | |||||||
| @@ -43,6 +43,7 @@ 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 | ||||||
|   resources.qrc |   resources.qrc | ||||||
| ) | ) | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										19
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								README.md
									
									
									
									
									
								
							| @@ -84,7 +84,9 @@ And then to optionally install it, run: | |||||||
| sudo cmake --install build | sudo cmake --install build | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| ## How it works | ## FAQ | ||||||
|  |  | ||||||
|  | ### 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) | ||||||
| @@ -93,6 +95,21 @@ 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`. 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 | ||||||
|   | |||||||
| @@ -1,5 +1,3 @@ | |||||||
| // From v0.4 |  | ||||||
|  |  | ||||||
| navigator.mediaDevices.chromiumGetDisplayMedia = | navigator.mediaDevices.chromiumGetDisplayMedia = | ||||||
|   navigator.mediaDevices.getDisplayMedia; |   navigator.mediaDevices.getDisplayMedia; | ||||||
|  |  | ||||||
| @@ -16,12 +14,12 @@ 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) | ||||||
|       console.log( |       userscript.log( | ||||||
|         `dsa: Did not find '${nameOfAudioDevice}', trying again in 100ms` |         `Did not find '${nameOfAudioDevice}', trying again in 100ms` | ||||||
|       ); |       ); | ||||||
|     await sleep(100); |     await sleep(100); | ||||||
|   } |   } | ||||||
|   console.log(`dsa: Found '${nameOfAudioDevice}'`); |   userscript.log(`Found '${nameOfAudioDevice}'`); | ||||||
|   return audioDevice; |   return audioDevice; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -71,6 +69,13 @@ function setGetDisplayMedia(video = true, overrideArgs = undefined) { | |||||||
|  |  | ||||||
| setGetDisplayMedia(); | setGetDisplayMedia(); | ||||||
|  |  | ||||||
|  | let userscript; | ||||||
|  | let muteBtn; | ||||||
|  | let deafenBtn; | ||||||
|  | let streamStartBtn; | ||||||
|  | let streamStartBtnInitialDisplay; | ||||||
|  | let streamStartBtnClone; | ||||||
|  | let resolutionString; | ||||||
| const clonedElements = []; | const clonedElements = []; | ||||||
| const hiddenElements = []; | const hiddenElements = []; | ||||||
| let wasStreamActive = false; | let wasStreamActive = false; | ||||||
| @@ -79,7 +84,7 @@ function createButton(text, onClick) { | |||||||
|   const button = document.createElement("button"); |   const button = document.createElement("button"); | ||||||
|   button.style.marginBottom = "20px"; |   button.style.marginBottom = "20px"; | ||||||
|   button.classList = |   button.classList = | ||||||
|     "button-f2h6uQ lookFilled-yCfaCM colorBrand-I6CyqQ sizeSmall-wU2dO- grow-2sR_-F"; |     "button-ejjZWC lookFilled-1H2Jvj colorBrand-2M3O3N sizeSmall-3R2P2p grow-2T4nbg"; | ||||||
|   button.innerText = text; |   button.innerText = text; | ||||||
|   button.addEventListener("click", onClick); |   button.addEventListener("click", onClick); | ||||||
|   return button; |   return button; | ||||||
| @@ -99,7 +104,7 @@ function createSwitch(text, enabled, onClick) { | |||||||
|   container.appendChild(svg); |   container.appendChild(svg); | ||||||
|  |  | ||||||
|   function setSvgDisabled() { |   function setSvgDisabled() { | ||||||
|     svg.innerHTML = `<div class="container-1QtPKm default-colors disabled-3_3z1m" style="opacity: 0.3; background-color: rgb(114, 118, 125);"><svg class="slider-HJFN2i" viewBox="0 0 28 20" preserveAspectRatio="xMinYMid meet" aria-hidden="true" style="left: -3px;"><rect fill="white" x="4" y="0" height="20" width="20" rx="10"></rect><svg viewBox="0 0 20 20" fill="none"><path fill="rgba(114, 118, 125, 1)" d="M5.13231 6.72963L6.7233 5.13864L14.855 13.2704L13.264 14.8614L5.13231 6.72963Z"></path><path fill="rgba(114, 118, 125, 1)" d="M13.2704 5.13864L14.8614 6.72963L6.72963 14.8614L5.13864 13.2704L13.2704 5.13864Z"></path></svg></svg><input id="uid_75" type="checkbox" class="input-125oad" tabindex="-1" disabled=""></div>`; |     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>`; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   function setSvgEnabled() { |   function setSvgEnabled() { | ||||||
| @@ -121,13 +126,43 @@ function createSwitch(text, enabled, onClick) { | |||||||
|   return container; |   return container; | ||||||
| } | } | ||||||
|  |  | ||||||
| setInterval(() => { | // Fix for broken discord notifications after restart | ||||||
|  | // (https://github.com/maltejur/discord-screenaudio/issues/17) | ||||||
|  | Notification.requestPermission(); | ||||||
|  |  | ||||||
|  | setTimeout(() => { | ||||||
|  |   new QWebChannel(qt.webChannelTransport, (channel) => { | ||||||
|  |     userscript = channel.objects.userscript; | ||||||
|  |     main(); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | function main() { | ||||||
|  |   userscript.muteToggled.connect(() => { | ||||||
|  |     muteBtn && muteBtn.click(); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   userscript.deafenToggled.connect(() => { | ||||||
|  |     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(); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   setInterval(async () => { | ||||||
|     const streamActive = |     const streamActive = | ||||||
|       document.getElementsByClassName("panel-2ZFCRb activityPanel-9icbyU") |       document.getElementsByClassName("panel-2ZFCRb activityPanel-9icbyU") | ||||||
|         .length > 0; |         .length > 0; | ||||||
|  |  | ||||||
|   if (!streamActive && wasStreamActive) |     if (!streamActive && wasStreamActive) userscript.stopVirtmic(); | ||||||
|     console.log("!discord-screenaudio-stream-stopped"); |  | ||||||
|     wasStreamActive = streamActive; |     wasStreamActive = streamActive; | ||||||
|  |  | ||||||
|     if (streamActive) { |     if (streamActive) { | ||||||
| @@ -142,66 +177,54 @@ setInterval(() => { | |||||||
|       hiddenElements.length = 0; |       hiddenElements.length = 0; | ||||||
|     } else { |     } else { | ||||||
|       for (const el of [ |       for (const el of [ | ||||||
|       document.getElementsByClassName("actionButtons-2vEOUh")?.[0]?.children[1], |         document.getElementsByClassName("actionButtons-2vEOUh")?.[0] | ||||||
|  |           ?.children[1], | ||||||
|         document.querySelector( |         document.querySelector( | ||||||
|           ".wrapper-3t3Yqv > div > div > div > div > .controlButton-2PMNom" |           ".wrapper-3t3Yqv > div > div > div > div > .controlButton-2PMNom" | ||||||
|         ), |         ), | ||||||
|       ]) { |       ]) { | ||||||
|         if (!el) continue; |         if (!el) continue; | ||||||
|         if (el.classList.contains("discord-screenaudio-cloned")) continue; |         if (el.classList.contains("discord-screenaudio-cloned")) continue; | ||||||
|       el.classList.add("discord-screenaudio-cloned"); |         streamStartBtn = el; | ||||||
|       elClone = el.cloneNode(true); |         streamStartBtn.classList.add("discord-screenaudio-cloned"); | ||||||
|       elClone.title = "Share Your Screen with Audio"; |  | ||||||
|       elClone.addEventListener("click", () => { |         streamStartBtnClone = streamStartBtn.cloneNode(true); | ||||||
|         console.log("!discord-screenaudio-start-stream"); |         streamStartBtnClone.title = "Share Your Screen with Audio"; | ||||||
|  |         streamStartBtnClone.addEventListener("click", () => { | ||||||
|  |           userscript.showStreamDialog(); | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|       const initialDisplay = el.style.display; |         streamStartBtnInitialDisplay = streamStartBtn.style.display; | ||||||
|  |  | ||||||
|       window.discordScreenaudioStartStream = ( |         streamStartBtn.style.display = "none"; | ||||||
|         video, |         streamStartBtn.parentNode.insertBefore(streamStartBtnClone, el); | ||||||
|         width, |  | ||||||
|         height, |  | ||||||
|         frameRate |  | ||||||
|       ) => { |  | ||||||
|         window.discordScreenaudioResolutionString = video |  | ||||||
|           ? `${height}p ${frameRate}FPS` |  | ||||||
|           : "Audio Only"; |  | ||||||
|         setGetDisplayMedia(video, { |  | ||||||
|           audio: true, |  | ||||||
|           video: { width, height, frameRate }, |  | ||||||
|         }); |  | ||||||
|         el.click(); |  | ||||||
|         el.style.display = initialDisplay; |  | ||||||
|         elClone.remove(); |  | ||||||
|       }; |  | ||||||
|  |  | ||||||
|       el.style.display = "none"; |         clonedElements.push(streamStartBtnClone); | ||||||
|       el.parentNode.insertBefore(elClone, el); |         hiddenElements.push(streamStartBtn); | ||||||
|  |  | ||||||
|       clonedElements.push(elClone); |  | ||||||
|       hiddenElements.push(el); |  | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Add about text in settings |     // Add about text in settings | ||||||
|     if ( |     if ( | ||||||
|     document.getElementsByClassName("dirscordScreenaudioAboutText").length == 0 |       document.getElementsByClassName("dirscordScreenaudioAboutText").length == | ||||||
|  |       0 | ||||||
|     ) { |     ) { | ||||||
|       for (const el of document.getElementsByClassName("info-3pQQBb")) { |       for (const el of document.getElementsByClassName("info-3pQQBb")) { | ||||||
|         let aboutEl; |         let aboutEl; | ||||||
|       if (window.discordScreenaudioKXMLGUI) { |         if (userscript.kxmlgui) { | ||||||
|           aboutEl = document.createElement("a"); |           aboutEl = document.createElement("a"); | ||||||
|           aboutEl.addEventListener("click", () => { |           aboutEl.addEventListener("click", () => { | ||||||
|           console.log("!discord-screenaudio-about"); |             userscript.showHelpMenu(); | ||||||
|           }); |           }); | ||||||
|         } else { |         } else { | ||||||
|           aboutEl = document.createElement("div"); |           aboutEl = document.createElement("div"); | ||||||
|         } |         } | ||||||
|       aboutEl.innerText = `discord-screenaudio ${window.discordScreenaudioVersion}`; |         aboutEl.innerText = `discord-screenaudio ${userscript.version}`; | ||||||
|         aboutEl.style.fontSize = "12px"; |         aboutEl.style.fontSize = "12px"; | ||||||
|         aboutEl.style.color = "var(--text-muted)"; |         aboutEl.style.color = "var(--text-muted)"; | ||||||
|         aboutEl.style.textTransform = "none"; |         aboutEl.style.textTransform = "none"; | ||||||
|  |         aboutEl.style.display = "inline-block"; | ||||||
|  |         aboutEl.style.width = "100%"; | ||||||
|         aboutEl.classList.add("dirscordScreenaudioAboutText"); |         aboutEl.classList.add("dirscordScreenaudioAboutText"); | ||||||
|         aboutEl.style.cursor = "pointer"; |         aboutEl.style.cursor = "pointer"; | ||||||
|         el.appendChild(aboutEl); |         el.appendChild(aboutEl); | ||||||
| @@ -227,7 +250,7 @@ setInterval(() => { | |||||||
|       div.style.marginBottom = "50px"; |       div.style.marginBottom = "50px"; | ||||||
|       div.appendChild( |       div.appendChild( | ||||||
|         createButton("Edit Global Keybinds", () => { |         createButton("Edit Global Keybinds", () => { | ||||||
|         console.log("!discord-screenaudio-keybinds"); |           userscript.showShortcutsDialog(); | ||||||
|         }) |         }) | ||||||
|       ); |       ); | ||||||
|       el.innerHTML = ""; |       el.innerHTML = ""; | ||||||
| @@ -237,31 +260,28 @@ setInterval(() => { | |||||||
|     const buttonContainer = |     const buttonContainer = | ||||||
|       document.getElementsByClassName("container-YkUktl")[0]; |       document.getElementsByClassName("container-YkUktl")[0]; | ||||||
|     if (!buttonContainer) { |     if (!buttonContainer) { | ||||||
|     console.log( |       userscript.log( | ||||||
|       "dsa: Cannot locate Mute/Deafen/Settings button container, please report this on GitHub" |         "Cannot locate Mute/Deafen/Settings button container, please report this on GitHub" | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   const muteBtn = buttonContainer |     muteBtn = buttonContainer | ||||||
|       ? buttonContainer.getElementsByClassName( |       ? buttonContainer.getElementsByClassName( | ||||||
|           "button-12Fmur enabled-9OeuTA button-f2h6uQ lookBlank-21BCro colorBrand-I6CyqQ grow-2sR_-F" |           "button-12Fmur enabled-9OeuTA button-f2h6uQ lookBlank-21BCro colorBrand-I6CyqQ grow-2sR_-F" | ||||||
|         )[0] |         )[0] | ||||||
|       : null; |       : null; | ||||||
|   window.discordScreenaudioToggleMute = () => muteBtn && muteBtn.click(); |  | ||||||
|  |  | ||||||
|   const deafenBtn = buttonContainer |     deafenBtn = buttonContainer | ||||||
|       ? buttonContainer.getElementsByClassName( |       ? buttonContainer.getElementsByClassName( | ||||||
|           "button-12Fmur enabled-9OeuTA button-f2h6uQ lookBlank-21BCro colorBrand-I6CyqQ grow-2sR_-F" |           "button-12Fmur enabled-9OeuTA button-f2h6uQ lookBlank-21BCro colorBrand-I6CyqQ grow-2sR_-F" | ||||||
|         )[1] |         )[1] | ||||||
|       : null; |       : null; | ||||||
|  |  | ||||||
|   window.discordScreenaudioToggleDeafen = () => deafenBtn && deafenBtn.click(); |     if (resolutionString) { | ||||||
|  |  | ||||||
|   if (window.discordScreenaudioResolutionString) { |  | ||||||
|       for (const el of document.getElementsByClassName( |       for (const el of document.getElementsByClassName( | ||||||
|         "qualityIndicator-39wQDy" |         "qualityIndicator-39wQDy" | ||||||
|       )) { |       )) { | ||||||
|       el.innerHTML = window.discordScreenaudioResolutionString; |         el.innerHTML = resolutionString; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -287,17 +307,26 @@ setInterval(() => { | |||||||
|  |  | ||||||
|           section.appendChild( |           section.appendChild( | ||||||
|             createButton("Edit Global Keybinds", () => { |             createButton("Edit Global Keybinds", () => { | ||||||
|             console.log("!discord-screenaudio-keybinds"); |               userscript.showShortcutsDialog(); | ||||||
|             }) |             }) | ||||||
|           ); |           ); | ||||||
|  |  | ||||||
|           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", | ||||||
|             window.discordScreenaudioTrayEnabled, |               await userscript.getBoolPref("trayIcon", false), | ||||||
|               (enabled) => { |               (enabled) => { | ||||||
|               window.discordScreenaudioTrayEnabled = enabled; |                 userscript.setTrayIcon(enabled); | ||||||
|               console.log(`!discord-screenaudio-tray-${enabled}`); |               } | ||||||
|  |             ) | ||||||
|  |           ); | ||||||
|  |  | ||||||
|  |           section.appendChild( | ||||||
|  |             createSwitch( | ||||||
|  |               "Start discord-screenaudio hidden to tray", | ||||||
|  |               await userscript.getPref("startHidden", false), | ||||||
|  |               (hidden) => { | ||||||
|  |                 userscript.setPref("startHidden", hidden); | ||||||
|               } |               } | ||||||
|             ) |             ) | ||||||
|           ); |           ); | ||||||
| @@ -311,7 +340,4 @@ setInterval(() => { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   }, 500); |   }, 500); | ||||||
|  | } | ||||||
| // Fix for broken discord notifications after restart |  | ||||||
| // (https://github.com/maltejur/discord-screenaudio/issues/17) |  | ||||||
| Notification.requestPermission(); |  | ||||||
|   | |||||||
							
								
								
									
										43
									
								
								assets/vencord/VencordNativeStub.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								assets/vencord/VencordNativeStub.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | 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); | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  | }; | ||||||
							
								
								
									
										14
									
								
								assets/vencord/plugin.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								assets/vencord/plugin.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | 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: [], | ||||||
|  | }); | ||||||
							
								
								
									
										15
									
								
								assets/vencord/settings.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								assets/vencord/settings.patch
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | --- 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> | ||||||
							
								
								
									
										410
									
								
								assets/vencord/vencord.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										410
									
								
								assets/vencord/vencord.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -2,6 +2,7 @@ | |||||||
| <RCC> | <RCC> | ||||||
|   <qresource> |   <qresource> | ||||||
|     <file>assets/userscript.js</file> |     <file>assets/userscript.js</file> | ||||||
|  |     <file>assets/vencord/vencord.js</file> | ||||||
|     <file>assets/de.shorsh.discord-screenaudio.png</file> |     <file>assets/de.shorsh.discord-screenaudio.png</file> | ||||||
|   </qresource> |   </qresource> | ||||||
| </RCC> | </RCC> | ||||||
							
								
								
									
										39
									
								
								scripts/build_vencord.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										39
									
								
								scripts/build_vencord.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | #!/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 | ||||||
| @@ -3,22 +3,10 @@ | |||||||
| #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 <QTimer> | #include <QTimer> | ||||||
| @@ -29,16 +17,40 @@ | |||||||
|  |  | ||||||
| DiscordPage::DiscordPage(QWidget *parent) : QWebEnginePage(parent) { | DiscordPage::DiscordPage(QWidget *parent) : QWebEnginePage(parent) { | ||||||
|   setBackgroundColor(QColor("#202225")); |   setBackgroundColor(QColor("#202225")); | ||||||
|   m_virtmicProcess.setProcessChannelMode(QProcess::ForwardedChannels); |  | ||||||
|  |  | ||||||
|   connect(this, &QWebEnginePage::featurePermissionRequested, this, |   connect(this, &QWebEnginePage::featurePermissionRequested, this, | ||||||
|           &DiscordPage::featurePermissionRequested); |           &DiscordPage::featurePermissionRequested); | ||||||
|  |  | ||||||
|   connect(this, &QWebEnginePage::loadStarted, [=]() { |   setupPermissions(); | ||||||
|     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"); | ||||||
|  |  | ||||||
|  |   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(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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, | ||||||
| @@ -50,76 +62,21 @@ DiscordPage::DiscordPage(QWidget *parent) : QWebEnginePage(parent) { | |||||||
|                            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("vars.js", |  | ||||||
|                    QString("window.discordScreenaudioVersion = '%1'; " |  | ||||||
|                            "window.discordScreenaudioTrayEnabled = %2;") |  | ||||||
|                        .arg(QApplication::applicationVersion()) |  | ||||||
|                        .arg(MainWindow::instance() |  | ||||||
|                                 ->settings() |  | ||||||
|                                 ->value("trayIcon", false) |  | ||||||
|                                 .toBool())); |  | ||||||
|  |  | ||||||
| #ifdef KXMLGUI |  | ||||||
|   injectScriptText("xmlgui.js", "window.discordScreenaudioKXMLGUI = true;"); |  | ||||||
|  |  | ||||||
|   KAboutData aboutData( |  | ||||||
|       "discord-screenaudio", "discord-screenaudio", |  | ||||||
|       QApplication::applicationVersion(), |  | ||||||
|       "Custom Discord client with the ability to stream audio on Linux", |  | ||||||
|       KAboutLicense::GPL_V3, "Copyright 2022 (C) Malte Jürgens"); |  | ||||||
|   aboutData.setBugAddress("https://github.com/maltejur/discord-screenaudio"); |  | ||||||
|   aboutData.addAuthor("Malte Jürgens", "Author", "maltejur@dismail.de", |  | ||||||
|                       "https://github.com/maltejur"); |  | ||||||
|   aboutData.addCredit("edisionnano", |  | ||||||
|                       "For creating and documenting the approach for streaming " |  | ||||||
|                       "audio in Discord used in this project.", |  | ||||||
|                       QString(), |  | ||||||
|                       "https://github.com/edisionnano/" |  | ||||||
|                       "Screenshare-with-audio-on-Discord-with-Linux"); |  | ||||||
|   aboutData.addCredit( |  | ||||||
|       "Curve", "For creating the Rohrkabel library used in this project.", |  | ||||||
|       QString(), "https://github.com/Curve"); |  | ||||||
|   aboutData.addComponent("Rohrkabel", "A C++ RAII Pipewire-API Wrapper", "1.3", |  | ||||||
|                          "https://github.com/Soundux/rohrkabel"); |  | ||||||
|   m_helpMenu = new KHelpMenu(parent, aboutData); |  | ||||||
|  |  | ||||||
| #ifdef KGLOBALACCEL |  | ||||||
|   injectScriptText("kglobalaccel.js", |  | ||||||
|                    "window.discordScreenaudioKGLOBALACCEL = true;"); |  | ||||||
|  |  | ||||||
|   auto toggleMuteAction = new QAction(this); |  | ||||||
|   toggleMuteAction->setText("Toggle Mute"); |  | ||||||
|   toggleMuteAction->setIcon(QIcon::fromTheme("microphone-sensitivity-muted")); |  | ||||||
|   connect(toggleMuteAction, &QAction::triggered, this, |  | ||||||
|           &DiscordPage::toggleMute); |  | ||||||
|  |  | ||||||
|   auto toggleDeafenAction = new QAction(this); |  | ||||||
|   toggleDeafenAction->setText("Toggle Deafen"); |  | ||||||
|   toggleDeafenAction->setIcon(QIcon::fromTheme("audio-volume-muted")); |  | ||||||
|   connect(toggleDeafenAction, &QAction::triggered, this, |  | ||||||
|           &DiscordPage::toggleDeafen); |  | ||||||
|  |  | ||||||
|   m_actionCollection = new KActionCollection(this); |  | ||||||
|   m_actionCollection->addAction("toggleMute", toggleMuteAction); |  | ||||||
|   KGlobalAccel::setGlobalShortcut(toggleMuteAction, QList<QKeySequence>{}); |  | ||||||
|   m_actionCollection->addAction("toggleDeafen", toggleDeafenAction); |  | ||||||
|   KGlobalAccel::setGlobalShortcut(toggleDeafenAction, QList<QKeySequence>{}); |  | ||||||
|  |  | ||||||
|   m_shortcutsDialog = new KShortcutsDialog(KShortcutsEditor::GlobalAction); |  | ||||||
|   m_shortcutsDialog->addCollection(m_actionCollection); |  | ||||||
| #endif |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
|   connect(&m_streamDialog, &StreamDialog::requestedStreamStart, this, |  | ||||||
|           &DiscordPage::startStream); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void DiscordPage::injectScriptText(QString name, QString content) { | void DiscordPage::setupUserStyles() { | ||||||
|  |   QString file = | ||||||
|  |       QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + | ||||||
|  |       "/userstyles.css"; | ||||||
|  |   if (QFileInfo(file).exists()) { | ||||||
|  |     qDebug(mainLog) << "Found userstyles:" << file; | ||||||
|  |     injectFile(&DiscordPage::injectStylesheet, "userstyles.js", file); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DiscordPage::injectScript( | ||||||
|  |     QString name, QString content, | ||||||
|  |     QWebEngineScript::InjectionPoint injectionPoint) { | ||||||
|   qDebug(mainLog) << "Injecting " << name; |   qDebug(mainLog) << "Injecting " << name; | ||||||
|  |  | ||||||
|   QWebEngineScript script; |   QWebEngineScript script; | ||||||
| @@ -127,20 +84,37 @@ void DiscordPage::injectScriptText(QString name, QString content) { | |||||||
|   script.setSourceCode(content); |   script.setSourceCode(content); | ||||||
|   script.setName(name); |   script.setName(name); | ||||||
|   script.setWorldId(QWebEngineScript::MainWorld); |   script.setWorldId(QWebEngineScript::MainWorld); | ||||||
|   script.setInjectionPoint(QWebEngineScript::DocumentCreation); |   script.setInjectionPoint(injectionPoint); | ||||||
|   script.setRunsOnSubFrames(false); |   script.setRunsOnSubFrames(false); | ||||||
|  |  | ||||||
|   scripts().insert(script); |   scripts().insert(script); | ||||||
| } | } | ||||||
|  |  | ||||||
| void DiscordPage::injectScriptFile(QString name, QString source) { | void DiscordPage::injectScript(QString name, QString content) { | ||||||
|  |   injectScript(name, content, QWebEngineScript::DocumentCreation); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void DiscordPage::injectStylesheet(QString name, QString content) { | ||||||
|  |   auto script = QString(R"(const stylesheet = document.createElement("style"); | ||||||
|  | stylesheet.type = "text/css"; | ||||||
|  | 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 { | ||||||
|     injectScriptText(name, file.readAll()); |     (this->*inject)(name, file.readAll()); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -151,11 +125,10 @@ void DiscordPage::featurePermissionRequested(const QUrl &securityOrigin, | |||||||
|                        QWebEnginePage::PermissionGrantedByUser); |                        QWebEnginePage::PermissionGrantedByUser); | ||||||
|  |  | ||||||
|   if (feature == QWebEnginePage::Feature::MediaAudioCapture) { |   if (feature == QWebEnginePage::Feature::MediaAudioCapture) { | ||||||
|     if (m_virtmicProcess.state() == QProcess::NotRunning) { |     if (!m_userScript.isVirtmicRunning()) { | ||||||
|       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_virtmicProcess.start(QApplication::arguments()[0], |       m_userScript.startVirtmic("None"); | ||||||
|                              {"--virtmic", "None"}); |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @@ -182,82 +155,81 @@ QWebEnginePage *DiscordPage::createWindow(QWebEnginePage::WebWindowType type) { | |||||||
|   return new ExternalPage; |   return new ExternalPage; | ||||||
| } | } | ||||||
|  |  | ||||||
| void DiscordPage::stopVirtmic() { | const QMap<QString, QString> cssAnsiColorMap = {{"black", "30"}, | ||||||
|   if (m_virtmicProcess.state() == QProcess::Running) { |                                                 {"red", "31"}, | ||||||
|     qDebug(virtmicLog) << "Stopping Virtmic"; |                                                 {"green", "32"}, | ||||||
|     m_virtmicProcess.kill(); |                                                 {"yellow", "33"}, | ||||||
|     m_virtmicProcess.waitForFinished(); |                                                 {"blue", "34"}, | ||||||
|   } |                                                 {"magenta", "35"}, | ||||||
| } |                                                 {"cyan", "36"}, | ||||||
|  |                                                 {"white", "37"}, | ||||||
| void DiscordPage::startVirtmic(QString target) { |                                                 {"gray", "90"}, | ||||||
|   qDebug(virtmicLog) << "Starting Virtmic with target" << target; |                                                 {"bright-red", "91"}, | ||||||
|   m_virtmicProcess.start(QApplication::arguments()[0], {"--virtmic", target}); |                                                 {"bright-green", "92"}, | ||||||
| } |                                                 {"bright-yellow", "93"}, | ||||||
|  |                                                 {"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) { | ||||||
|   if (message == "!discord-screenaudio-start-stream") { |   auto colorSegments = message.split("%c"); | ||||||
|     if (m_streamDialog.isHidden()) |   for (auto segment : colorSegments.mid(1)) { | ||||||
|       m_streamDialog.setHidden(false); |     auto lines = segment.split("\n"); | ||||||
|     else |     QString ansi; | ||||||
|       m_streamDialog.activateWindow(); |     uint endOfStyles = lines.length(); | ||||||
|     m_streamDialog.updateTargets(); |     for (size_t line = 1; line < lines.length(); line++) { | ||||||
|   } else if (message == "!discord-screenaudio-stream-stopped") { |       if (!lines[line].endsWith(";")) { | ||||||
|     stopVirtmic(); |         endOfStyles = line; | ||||||
|   } else if (message == "!discord-screenaudio-about") { |         break; | ||||||
| #ifdef KXMLGUI |       } | ||||||
|     m_helpMenu->aboutApplication(); |       if (lines[line] == "font-weight: bold;") | ||||||
| #endif |         ansi += "\033[1m"; | ||||||
|   } else if (message == "!discord-screenaudio-keybinds") { |       else if (lines[line].startsWith("color: ")) { | ||||||
| #ifdef KXMLGUI |         auto color = lines[line].mid(7).chopped(1); | ||||||
| #ifdef KGLOBALACCEL |         if (cssAnsiColorMap.find(color) != cssAnsiColorMap.end()) | ||||||
|     m_shortcutsDialog->show(); |           ansi += "\033[" + cssAnsiColorMap[color] + "m"; | ||||||
| #else |  | ||||||
|     QMessageBox::information(MainWindow::instance(), "discord-screenaudio", |  | ||||||
|                              "Keybinds are not supported on this platform " |  | ||||||
|                              "(KGlobalAccel is not available).", |  | ||||||
|                              QMessageBox::Ok); |  | ||||||
| #endif |  | ||||||
| #else |  | ||||||
|     QMessageBox::information(MainWindow::instance(), "discord-screenaudio", |  | ||||||
|                              "Keybinds are not supported on this platform " |  | ||||||
|                              "(KXmlGui and KGlobalAccel are not available).", |  | ||||||
|                              QMessageBox::Ok); |  | ||||||
| #endif |  | ||||||
|   } else if (message == "!discord-screenaudio-tray-true") { |  | ||||||
|     MainWindow::instance()->setTrayIcon(true); |  | ||||||
|   } else if (message == "!discord-screenaudio-tray-false") { |  | ||||||
|     MainWindow::instance()->setTrayIcon(false); |  | ||||||
|   } else if (message.startsWith("dsa: ")) { |  | ||||||
|     qDebug(userscriptLog) << message.mid(5).toUtf8().constData(); |  | ||||||
|   } else { |  | ||||||
|     qDebug(discordLog) << message; |  | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |     qDebug(discordLog) << (ansi + lines[0].trimmed() + "\033[0m " + | ||||||
| void DiscordPage::startStream(bool video, bool audio, uint width, uint height, |                            ((lines.length() > endOfStyles) | ||||||
|                               uint frameRate, QString target) { |                                 ? lines[endOfStyles].trimmed() | ||||||
|   stopVirtmic(); |                                 : "")) | ||||||
|   startVirtmic(audio ? target : "[None]"); |                               .toUtf8() | ||||||
|   // Wait a bit for the virtmic to start |                               .constData(); | ||||||
|   QTimer::singleShot(200, [=]() { |     for (auto line : lines.mid(endOfStyles + 1)) { | ||||||
|     runJavaScript( |       qDebug(discordLog) << line.toUtf8().constData(); | ||||||
|         QString("window.discordScreenaudioStartStream(%1, %2, %3, %4);") |  | ||||||
|             .arg(video) |  | ||||||
|             .arg(video ? width : 32) |  | ||||||
|             .arg(video ? height : 16) |  | ||||||
|             .arg(video ? frameRate : 1)); |  | ||||||
|   }); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
| void DiscordPage::toggleMute() { |  | ||||||
|   qDebug(shortcutLog) << "Toggling mute"; |  | ||||||
|   runJavaScript("window.discordScreenaudioToggleMute();"); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
| void DiscordPage::toggleDeafen() { |  | ||||||
|   qDebug(shortcutLog) << "Toggling deafen"; |  | ||||||
|   runJavaScript("window.discordScreenaudioToggleDeafen();"); |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,17 +1,12 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include "streamdialog.h" | #include "streamdialog.h" | ||||||
|  | #include "userscript.h" | ||||||
| #include "virtmic.h" | #include "virtmic.h" | ||||||
|  |  | ||||||
| #ifdef KXMLGUI |  | ||||||
| #include <KActionCollection> |  | ||||||
| #include <KHelpMenu> |  | ||||||
| #include <KShortcutsDialog> |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| #include <QProcess> |  | ||||||
| #include <QWebEngineFullScreenRequest> | #include <QWebEngineFullScreenRequest> | ||||||
| #include <QWebEnginePage> | #include <QWebEnginePage> | ||||||
|  | #include <QWebEngineScript> | ||||||
|  |  | ||||||
| class DiscordPage : public QWebEnginePage { | class DiscordPage : public QWebEnginePage { | ||||||
|   Q_OBJECT |   Q_OBJECT | ||||||
| @@ -20,15 +15,9 @@ public: | |||||||
|   explicit DiscordPage(QWidget *parent = nullptr); |   explicit DiscordPage(QWidget *parent = nullptr); | ||||||
|  |  | ||||||
| private: | private: | ||||||
|   StreamDialog m_streamDialog; |   UserScript m_userScript; | ||||||
|   QProcess m_virtmicProcess; |   void setupPermissions(); | ||||||
| #ifdef KXMLGUI |   void setupUserStyles(); | ||||||
|   KHelpMenu *m_helpMenu; |  | ||||||
| #ifdef KGLOBALACCEL |  | ||||||
|   KActionCollection *m_actionCollection; |  | ||||||
|   KShortcutsDialog *m_shortcutsDialog; |  | ||||||
| #endif |  | ||||||
| #endif |  | ||||||
|   bool acceptNavigationRequest(const QUrl &url, |   bool acceptNavigationRequest(const QUrl &url, | ||||||
|                                QWebEnginePage::NavigationType type, |                                QWebEnginePage::NavigationType type, | ||||||
|                                bool isMainFrame) override; |                                bool isMainFrame) override; | ||||||
| @@ -37,18 +26,16 @@ 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 injectScriptText(QString name, QString content); |   void injectScript(QString name, QString content, | ||||||
|   void injectScriptFile(QString name, QString source); |                     QWebEngineScript::InjectionPoint injectionPoint); | ||||||
|   void stopVirtmic(); |   void injectScript(QString name, QString content); | ||||||
|   void startVirtmic(QString target); |   void injectStylesheet(QString name, QString content); | ||||||
|   void toggleMute(); |   void injectFile(void (DiscordPage::*inject)(QString, QString), QString name, | ||||||
|   void toggleDeafen(); |                   QString source); | ||||||
|  |  | ||||||
| private Q_SLOTS: | private Q_SLOTS: | ||||||
|   void featurePermissionRequested(const QUrl &securityOrigin, |   void featurePermissionRequested(const QUrl &securityOrigin, | ||||||
|                                   QWebEnginePage::Feature feature); |                                   QWebEnginePage::Feature feature); | ||||||
|   void startStream(bool video, bool audio, uint width, uint height, |  | ||||||
|                    uint frameRate, QString target); |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // Will immediately get destroyed again but is needed for navigation to | // Will immediately get destroyed again but is needed for navigation to | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ | |||||||
| #include <QPushButton> | #include <QPushButton> | ||||||
| #include <QSpacerItem> | #include <QSpacerItem> | ||||||
| #include <QThread> | #include <QThread> | ||||||
|  | #include <QTimer> | ||||||
| #include <QUrl> | #include <QUrl> | ||||||
| #include <QWebEngineNotification> | #include <QWebEngineNotification> | ||||||
| #include <QWebEngineProfile> | #include <QWebEngineProfile> | ||||||
| @@ -34,6 +35,11 @@ MainWindow::MainWindow(bool useNotifySend, QWidget *parent) | |||||||
|   setupTrayIcon(); |   setupTrayIcon(); | ||||||
|   resize(1000, 700); |   resize(1000, 700); | ||||||
|   showMaximized(); |   showMaximized(); | ||||||
|  |   if (m_settings->value("trayIcon", false).toBool() && | ||||||
|  |       m_settings->value("startHidden", false).toBool()) { | ||||||
|  |     hide(); | ||||||
|  |     QTimer::singleShot(0, [=]() { hide(); }); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void MainWindow::setupWebView() { | void MainWindow::setupWebView() { | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ | |||||||
| #include <QSizePolicy> | #include <QSizePolicy> | ||||||
| #include <QVBoxLayout> | #include <QVBoxLayout> | ||||||
|  |  | ||||||
| StreamDialog::StreamDialog() : QWidget() { | StreamDialog::StreamDialog(QWidget *parent) : QDialog(parent) { | ||||||
|   setAttribute(Qt::WA_QuitOnClose, false); |   setAttribute(Qt::WA_QuitOnClose, false); | ||||||
|  |  | ||||||
|   { |   { | ||||||
| @@ -22,8 +22,7 @@ StreamDialog::StreamDialog() : QWidget() { | |||||||
|     layout->addWidget(m_videoGroupBox); |     layout->addWidget(m_videoGroupBox); | ||||||
|  |  | ||||||
|     { |     { | ||||||
|       auto videoLayout = new QVBoxLayout(this); |       auto videoLayout = new QVBoxLayout(m_videoGroupBox); | ||||||
|       m_videoGroupBox->setLayout(videoLayout); |  | ||||||
|  |  | ||||||
|       auto resolutionLabel = new QLabel(this); |       auto resolutionLabel = new QLabel(this); | ||||||
|       resolutionLabel->setText("Resolution"); |       resolutionLabel->setText("Resolution"); | ||||||
| @@ -60,15 +59,14 @@ StreamDialog::StreamDialog() : QWidget() { | |||||||
|     layout->addWidget(m_audioGroupBox); |     layout->addWidget(m_audioGroupBox); | ||||||
|  |  | ||||||
|     { |     { | ||||||
|       auto audioLayout = new QVBoxLayout(this); |       auto audioLayout = new QVBoxLayout(m_audioGroupBox); | ||||||
|       m_audioGroupBox->setLayout(audioLayout); |  | ||||||
|  |  | ||||||
|       auto targetLabel = new QLabel(this); |       auto targetLabel = new QLabel(this); | ||||||
|       targetLabel->setText("Audio Source"); |       targetLabel->setText("Audio Source"); | ||||||
|       audioLayout->addWidget(targetLabel); |       audioLayout->addWidget(targetLabel); | ||||||
|  |  | ||||||
|       { |       { | ||||||
|         auto targetLayout = new QHBoxLayout(this); |         auto targetLayout = new QHBoxLayout(); | ||||||
|         audioLayout->addLayout(targetLayout); |         audioLayout->addLayout(targetLayout); | ||||||
|  |  | ||||||
|         m_targetComboBox = new QComboBox(this); |         m_targetComboBox = new QComboBox(this); | ||||||
| @@ -88,8 +86,6 @@ StreamDialog::StreamDialog() : QWidget() { | |||||||
|     button->setText("Start Stream"); |     button->setText("Start Stream"); | ||||||
|     connect(button, &QPushButton::clicked, this, &StreamDialog::startStream); |     connect(button, &QPushButton::clicked, this, &StreamDialog::startStream); | ||||||
|     layout->addWidget(button, Qt::AlignRight | Qt::AlignBottom); |     layout->addWidget(button, Qt::AlignRight | Qt::AlignBottom); | ||||||
|  |  | ||||||
|     setLayout(layout); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   setWindowTitle("discord-screenaudio Stream Dialog"); |   setWindowTitle("discord-screenaudio Stream Dialog"); | ||||||
| @@ -98,9 +94,9 @@ StreamDialog::StreamDialog() : QWidget() { | |||||||
| void StreamDialog::startStream() { | void StreamDialog::startStream() { | ||||||
|   auto resolution = m_resolutionComboBox->currentData().toString().split('x'); |   auto resolution = m_resolutionComboBox->currentData().toString().split('x'); | ||||||
|   emit requestedStreamStart(m_videoGroupBox->isChecked(), |   emit requestedStreamStart(m_videoGroupBox->isChecked(), | ||||||
|                             m_audioGroupBox->isChecked(), |                             m_audioGroupBox->isChecked(), resolution[0].toInt(), | ||||||
|                             resolution[0].toUInt(), resolution[1].toUInt(), |                             resolution[1].toInt(), | ||||||
|                             m_framerateComboBox->currentData().toUInt(), |                             m_framerateComboBox->currentData().toInt(), | ||||||
|                             m_targetComboBox->currentText()); |                             m_targetComboBox->currentText()); | ||||||
|   setHidden(true); |   setHidden(true); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,11 +5,11 @@ | |||||||
| #include <QGroupBox> | #include <QGroupBox> | ||||||
| #include <QWidget> | #include <QWidget> | ||||||
|  |  | ||||||
| class StreamDialog : public QWidget { | class StreamDialog : public QDialog { | ||||||
|   Q_OBJECT |   Q_OBJECT | ||||||
|  |  | ||||||
| public: | public: | ||||||
|   explicit StreamDialog(); |   explicit StreamDialog(QWidget *parent = nullptr); | ||||||
|  |  | ||||||
| private: | private: | ||||||
|   QComboBox *m_targetComboBox; |   QComboBox *m_targetComboBox; | ||||||
| @@ -19,8 +19,8 @@ private: | |||||||
|   QGroupBox *m_audioGroupBox; |   QGroupBox *m_audioGroupBox; | ||||||
|  |  | ||||||
| Q_SIGNALS: | Q_SIGNALS: | ||||||
|   void requestedStreamStart(bool video, bool audio, uint width, uint height, |   void requestedStreamStart(bool video, bool audio, int width, int height, | ||||||
|                             uint frameRate, QString target); |                             int frameRate, QString target); | ||||||
|  |  | ||||||
| public Q_SLOTS: | public Q_SLOTS: | ||||||
|   void updateTargets(); |   void updateTargets(); | ||||||
|   | |||||||
							
								
								
									
										234
									
								
								src/userscript.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										234
									
								
								src/userscript.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,234 @@ | |||||||
|  | #include "userscript.h" | ||||||
|  | #include "log.h" | ||||||
|  | #include "mainwindow.h" | ||||||
|  |  | ||||||
|  | #include <QApplication> | ||||||
|  | #include <QDebug> | ||||||
|  | #include <QDesktopServices> | ||||||
|  | #include <QDir> | ||||||
|  | #include <QFile> | ||||||
|  | #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"); | ||||||
|  |   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(toggleMuteAction, &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(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | QVariant UserScript::vencordSend(QString event, QVariantList args) { | ||||||
|  |   QString configFolder = | ||||||
|  |       QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + | ||||||
|  |       "/vencord"; | ||||||
|  |   QString quickCssFile = configFolder + "/quickCss.css"; | ||||||
|  |   QString settingsFile = configFolder + "/settings.json"; | ||||||
|  |  | ||||||
|  |   if (!QDir().exists(configFolder)) | ||||||
|  |     QDir().mkpath(configFolder); | ||||||
|  |  | ||||||
|  |   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); | ||||||
|  | } | ||||||
							
								
								
									
										71
									
								
								src/userscript.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/userscript.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | |||||||
|  | #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 | ||||||
|  |  | ||||||
|  | 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); | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |   QProcess m_virtmicProcess; | ||||||
|  |   StreamDialog *m_streamDialog; | ||||||
|  |   bool m_kxmlgui = false; | ||||||
|  |   bool m_kglobalaccel = false; | ||||||
|  | #ifdef KXMLGUI | ||||||
|  |   KHelpMenu *m_helpMenu; | ||||||
|  | #ifdef KGLOBALACCEL | ||||||
|  |   KActionCollection *m_actionCollection; | ||||||
|  |   KShortcutsDialog *m_shortcutsDialog; | ||||||
|  | #endif | ||||||
|  | #endif | ||||||
|  |   QString m_vencordSettings; | ||||||
|  |   void setupHelpMenu(); | ||||||
|  |   void setupShortcutsDialog(); | ||||||
|  |   void setupStreamDialog(); | ||||||
|  |   void setupVirtmic(); | ||||||
|  |  | ||||||
|  | Q_SIGNALS: | ||||||
|  |   void muteToggled(); | ||||||
|  |   void deafenToggled(); | ||||||
|  |   void streamStarted(bool video, int width, int height, int frameRate); | ||||||
|  |  | ||||||
|  | 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 stopVirtmic(); | ||||||
|  |   void startVirtmic(QString target); | ||||||
|  |   QVariant vencordSend(QString event, QVariantList args); | ||||||
|  |  | ||||||
|  | private Q_SLOTS: | ||||||
|  |   void startStream(bool video, bool audio, int width, int height, int frameRate, | ||||||
|  |                    QString target); | ||||||
|  | }; | ||||||
		Reference in New Issue
	
	Block a user