Merge branch 'main' into integrated-tray-icon
This commit is contained in:
		
						commit
						879b1fd58e
					
				
					 39 changed files with 5390 additions and 3771 deletions
				
			
		
							
								
								
									
										32
									
								
								.github/ISSUE_TEMPLATE/bug_report.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										32
									
								
								.github/ISSUE_TEMPLATE/bug_report.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -13,15 +13,30 @@ body: | ||||||
| 
 | 
 | ||||||
|               Make sure both Vesktop and Vencord are fully up to date. You can update Vencord by right-clicking the Vesktop tray icon and pressing "Update Vencord" |               Make sure both Vesktop and Vencord are fully up to date. You can update Vencord by right-clicking the Vesktop tray icon and pressing "Update Vencord" | ||||||
| 
 | 
 | ||||||
|               Do not report any of the following issues: |               **DO NOT REPORT** any of the following issues: | ||||||
|               - Purely graphical glitches like flickering, scaling issues, etc: Issue with your gpu. Nothing we can do, update drivers or disable hardware acceleration |               - Purely graphical glitches like flickering, scaling issues[^1] | ||||||
|  |               - App crashing / not showing window with mentions of the gpu process in the stacktrace[^1] | ||||||
|  |               - Screenshare not starting, black screening or crashing[^2] | ||||||
|               - Vencord related issues: This is the Vesktop repo, not Vencord |               - Vencord related issues: This is the Vesktop repo, not Vencord | ||||||
|               - Screenshare not starting / black screening on Linux: Issue with your desktop environment, specifically its xdg-desktop-portal |               - Captchas[^3] | ||||||
|  |               - Issues with opening URLs[^4] | ||||||
|  |               - Issues with Notifications[^4] | ||||||
|  |               - Issues with Input Methods[^4] | ||||||
|  |               - Issues with File Drag and Drop[^5]  | ||||||
|  |               - Network Errors[^6] | ||||||
| 
 | 
 | ||||||
|               Linux users: Please only report issues with supported packages (flatpak and any builds from the README / releases). |               Linux users: Please only report issues with supported packages (flatpak and any builds from the README / releases). | ||||||
|               We do not support other packages, like the AUR or Nix packages, so please first make sure your issue is reproducible with official releases, |               We do not support other packages, like the AUR or Nix packages, so please first make sure your issue is reproducible with official releases, | ||||||
|               like [our Flatpak](https://flathub.org/apps/dev.vencord.Vesktop) or [AppImage](https://vencord.dev/download/vesktop/amd64/appimage) |               like [our Flatpak](https://flathub.org/apps/dev.vencord.Vesktop) or [AppImage](https://vencord.dev/download/vesktop/amd64/appimage) | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  |               [^1]: GPU issue. Disable hardware acceleration in Vesktop Settings or run with `--disable-gpu` | ||||||
|  |               [^2]: System issue. You will have to fix it | ||||||
|  |               [^3]: If you are receiving a lot of captchas, it means Discord thinks you might be a bot. Make sure you're not using a VPN/Proxy | ||||||
|  |               [^4]: These things are handled by Chromium / Electron, not us. If they don't work, it's either an issue with your system or a bug with Chromium. | ||||||
|  |               [^5]: You are likely using the Vesktop flatpak and trying to drop a file the flatpak can't access. You can fix this by installing Flatseal and using it to grant Vesktop full access to your files | ||||||
|  |               [^6]: Issue on your end, you have to fix it. Try changing your DNS to [1.1.1.1 (Cloudflare DNS)](https://developers.cloudflare.com/1.1.1.1/setup/) | ||||||
|  |                | ||||||
|     - type: input |     - type: input | ||||||
|       id: discord |       id: discord | ||||||
|       attributes: |       attributes: | ||||||
|  | @ -49,6 +64,15 @@ body: | ||||||
|       validations: |       validations: | ||||||
|           required: false |           required: false | ||||||
| 
 | 
 | ||||||
|  |     - type: input | ||||||
|  |       id: install-type | ||||||
|  |       attributes: | ||||||
|  |           label: Package Type | ||||||
|  |           description: What kind of Vesktop package are you using? (Setup exe, Portable, Flatpak, AppImage, Deb, etc) | ||||||
|  |           placeholder: Flatpak | ||||||
|  |       validations: | ||||||
|  |           required: true | ||||||
|  | 
 | ||||||
|     - type: textarea |     - type: textarea | ||||||
|       id: bug-description |       id: bug-description | ||||||
|       attributes: |       attributes: | ||||||
|  | @ -84,7 +108,7 @@ body: | ||||||
|       id: debug-logs |       id: debug-logs | ||||||
|       attributes: |       attributes: | ||||||
|           label: Debug Logs |           label: Debug Logs | ||||||
|           description: Run vesktop from the command line. Include the relevant command line output here |           description: Run vesktop from the command line. Include the relevant command line output here. If there are any lines that seem relevant, try googling them or searching existing issues | ||||||
|           value: | |           value: | | ||||||
|               ``` |               ``` | ||||||
|               Replace this text with your crash-log. Do not remove the backticks |               Replace this text with your crash-log. Do not remove the backticks | ||||||
|  |  | ||||||
							
								
								
									
										6
									
								
								.github/ISSUE_TEMPLATE/feature-request.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/ISSUE_TEMPLATE/feature-request.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -1,7 +1,7 @@ | ||||||
| name: 🛠️ Feature Request | name: 🛠️ Feature Request | ||||||
| description: Create a feature request for Vesktop | description: Request a feature for Vesktop | ||||||
| labels: [bug] | labels: [enhancement] | ||||||
| title: "[Bug] <title>" | title: "[Feature Request] <title>" | ||||||
| 
 | 
 | ||||||
| body: | body: | ||||||
|     - type: markdown |     - type: markdown | ||||||
|  |  | ||||||
							
								
								
									
										42
									
								
								.github/workflows/meta.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										42
									
								
								.github/workflows/meta.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -11,28 +11,28 @@ jobs: | ||||||
|         runs-on: ubuntu-latest |         runs-on: ubuntu-latest | ||||||
| 
 | 
 | ||||||
|         steps: |         steps: | ||||||
|         - uses: actions/checkout@v3 |             - uses: actions/checkout@v4 | ||||||
|         - uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json |             - uses: pnpm/action-setup@v4 # Install pnpm using packageManager key in package.json | ||||||
| 
 | 
 | ||||||
|         - name: Use Node.js 18.18.2 |             - name: Use Node.js 20 | ||||||
|           uses: actions/setup-node@v3 |               uses: actions/setup-node@v4 | ||||||
|           with: |               with: | ||||||
|               node-version: 18.18.2 |                   node-version: 20 | ||||||
| 
 | 
 | ||||||
|         - name: Install dependencies |             - name: Install dependencies | ||||||
|           run: pnpm i |               run: pnpm i | ||||||
| 
 | 
 | ||||||
|         - name: Update metainfo |             - name: Update metainfo | ||||||
|           run: pnpm updateMeta |               run: pnpm updateMeta | ||||||
| 
 | 
 | ||||||
|         - name: Commit and merge in changes |             - name: Commit and merge in changes | ||||||
|           run: | |               run: | | ||||||
|               git config user.name "github-actions[bot]" |                   git config user.name "github-actions[bot]" | ||||||
|               git config user.email "41898282+github-actions[bot]@users.noreply.github.com" |                   git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | ||||||
|               git checkout -b ci/meta-update |                   git checkout -b ci/meta-update | ||||||
|               git add meta/dev.vencord.Vesktop.metainfo.xml |                   git add meta/dev.vencord.Vesktop.metainfo.xml | ||||||
|               git commit -m "Insert release changes for ${{ github.event.release.tag_name }}" |                   git commit -m "Insert release changes for ${{ github.event.release.tag_name }}" | ||||||
|               git push origin ci/meta-update |                   git push origin ci/meta-update | ||||||
|               gh pr create -B main -H ci/meta-update -t "Metainfo for ${{ github.event.release.tag_name }}" -b "This PR updates the metainfo for release ${{ github.event.release.tag_name }}. @lewisakura @Vendicated" |                   gh pr create -B main -H ci/meta-update -t "Metainfo for ${{ github.event.release.tag_name }}" -b "This PR updates the metainfo for release ${{ github.event.release.tag_name }}. @lewisakura @Vendicated" | ||||||
|           env: |               env: | ||||||
|               GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |                   GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||||
|  |  | ||||||
							
								
								
									
										19
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -22,13 +22,13 @@ jobs: | ||||||
|                       platform: windows |                       platform: windows | ||||||
| 
 | 
 | ||||||
|         steps: |         steps: | ||||||
|             - uses: actions/checkout@v3 |             - uses: actions/checkout@v4 | ||||||
|             - uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json |             - uses: pnpm/action-setup@v4 # Install pnpm using packageManager key in package.json | ||||||
| 
 | 
 | ||||||
|             - name: Use Node.js 18.18.2 |             - name: Use Node.js 20 | ||||||
|               uses: actions/setup-node@v3 |               uses: actions/setup-node@v4 | ||||||
|               with: |               with: | ||||||
|                   node-version: 18.18.2 |                   node-version: 20 | ||||||
|                   cache: "pnpm" |                   cache: "pnpm" | ||||||
| 
 | 
 | ||||||
|             - name: Install dependencies |             - name: Install dependencies | ||||||
|  | @ -47,7 +47,12 @@ jobs: | ||||||
|             - name: Run Electron Builder |             - name: Run Electron Builder | ||||||
|               if: ${{ matrix.platform == 'mac' }} |               if: ${{ matrix.platform == 'mac' }} | ||||||
|               run: | |               run: | | ||||||
|  |                   echo "$API_KEY" > apple.p8 | ||||||
|                   pnpm electron-builder --${{ matrix.platform }} --publish always |                   pnpm electron-builder --${{ matrix.platform }} --publish always | ||||||
|               env: |               env: | ||||||
|                   GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |                 GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||||
|                   CSC_LINK: ${{ secrets.APPLE_SIGNING_CERT }} |                 CSC_LINK: ${{ secrets.APPLE_SIGNING_CERT }} | ||||||
|  |                 API_KEY: ${{ secrets.APPLE_API_KEY }} | ||||||
|  |                 APPLE_API_KEY: apple.p8 | ||||||
|  |                 APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} | ||||||
|  |                 APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} | ||||||
|  |  | ||||||
							
								
								
									
										10
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -11,13 +11,13 @@ jobs: | ||||||
|         runs-on: ubuntu-latest |         runs-on: ubuntu-latest | ||||||
| 
 | 
 | ||||||
|         steps: |         steps: | ||||||
|             - uses: actions/checkout@v3 |             - uses: actions/checkout@v4 | ||||||
|             - uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json |             - uses: pnpm/action-setup@v4 # Install pnpm using packageManager key in package.json | ||||||
| 
 | 
 | ||||||
|             - name: Use Node.js 18.18.2 |             - name: Use Node.js 20 | ||||||
|               uses: actions/setup-node@v3 |               uses: actions/setup-node@v4 | ||||||
|               with: |               with: | ||||||
|                   node-version: 18.18.2 |                   node-version: 20 | ||||||
|                   cache: "pnpm" |                   cache: "pnpm" | ||||||
| 
 | 
 | ||||||
|             - name: Install dependencies |             - name: Install dependencies | ||||||
|  |  | ||||||
							
								
								
									
										4
									
								
								.github/workflows/winget-submission.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/winget-submission.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -13,10 +13,10 @@ on: | ||||||
| jobs: | jobs: | ||||||
|   winget: |   winget: | ||||||
|     name: Publish winget package |     name: Publish winget package | ||||||
|     runs-on: ubuntu-latest |     runs-on: windows-latest | ||||||
|     steps: |     steps: | ||||||
|       - name: Submit package to Winget Community Repo |       - name: Submit package to Winget Community Repo | ||||||
|         uses: vedantmgoyal2009/winget-releaser@e68d386d5d6a1cef8cb0fb5e62b77ebcb83e7d58 # v2 |         uses: vedantmgoyal2009/winget-releaser@4614300d5812e5df91cb02ef0edbece623d5dea8 | ||||||
|         with: |         with: | ||||||
|           identifier: Vencord.Vesktop |           identifier: Vencord.Vesktop | ||||||
|           token: ${{ secrets.WINGET_PAT }} |           token: ${{ secrets.WINGET_PAT }} | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								.npmrc
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								.npmrc
									
									
									
									
									
								
							|  | @ -1 +1,2 @@ | ||||||
| node-linker=hoisted | node-linker=hoisted | ||||||
|  | package-manager-strict=false | ||||||
							
								
								
									
										11
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								README.md
									
									
									
									
									
								
							|  | @ -21,15 +21,14 @@ Vesktop is a custom Discord desktop app | ||||||
| 
 | 
 | ||||||
| If you don't know the difference, pick the Installer. | If you don't know the difference, pick the Installer. | ||||||
| 
 | 
 | ||||||
| - [Installer](https://vencord.dev/download/vesktop/amd64/windows) | - [Installer](https://vencord.dev/download/vesktop/universal/windows) | ||||||
| - [Portable](https://vencord.dev/download/vesktop/amd64/windows-portable) | - Portable: | ||||||
|  |   - [x64 / amd64](https://vencord.dev/download/vesktop/amd64/windows-portable) | ||||||
|  |   - [arm64](https://vencord.dev/download/vesktop/arm64/windows-portable) | ||||||
| 
 | 
 | ||||||
| ### Mac | ### Mac | ||||||
| 
 | 
 | ||||||
| If you don't know the difference, pick the Intel build. | [Vesktop.dmg](https://vencord.dev/download/vesktop/universal/dmg) | ||||||
| 
 |  | ||||||
| - [Intel build (amd64)](https://vencord.dev/download/vesktop/amd64/dmg) |  | ||||||
| - [Apple Silicon (arm64)](https://vencord.dev/download/vesktop/arm64/dmg) |  | ||||||
| 
 | 
 | ||||||
| ### Linux | ### Linux | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -28,6 +28,34 @@ | ||||||
|     </screenshot> |     </screenshot> | ||||||
|   </screenshots> |   </screenshots> | ||||||
|   <releases> |   <releases> | ||||||
|  |     <release version="1.5.3" date="2024-07-04" type="stable"> | ||||||
|  |       <url>https://github.com/Vencord/Vesktop/releases/tag/v1.5.3</url> | ||||||
|  |       <description> | ||||||
|  |         <p>Features</p> | ||||||
|  |         <ul> | ||||||
|  |           <li>added arm64 Windows support</li> | ||||||
|  |           <li>windows & macOS builds are now universal</li> | ||||||
|  |           <li>added option to configure spellcheck languages</li> | ||||||
|  |           <li>will auto-update from now on</li> | ||||||
|  |           <li>updated electron to 31 & Chromium to 126</li> | ||||||
|  |           <li>macOS: Added customized dmg background by @khcrysalis</li> | ||||||
|  |           <li>Windows Portable: store settings in portable folder by @MrGarlic1</li> | ||||||
|  |           <li>linux audioshare: added granular selection, more options, better ui by @Curve</li> | ||||||
|  |           <li>changed default screen-sharing quality to 720p 30 FPS by @Tiagoquix</li> | ||||||
|  |         </ul> | ||||||
|  |         <p>Fixes</p> | ||||||
|  |         <ul> | ||||||
|  |           <li>macOS: Added workaround for making things in draggable area clickable by @HAHALOSAH</li> | ||||||
|  |           <li>fixed Screenshare UI for non-linux systems by @PolisanTheEasyNick</li> | ||||||
|  |           <li>fixed opening on screen that was disconnected by @MrGarlic1</li> | ||||||
|  |           <li>mac: hide native window controls with custom titlebar enabled by @MrGarlic1</li> | ||||||
|  |           <li>fixed some broken patches by @D3SOX</li> | ||||||
|  |           <li>fixed framerate in constraints by @kittykel</li> | ||||||
|  |           <li>fixed some first launch switches not applying</li> | ||||||
|  |           <li>fixed potential sandbox escape via custom vencord location</li> | ||||||
|  |         </ul> | ||||||
|  |       </description> | ||||||
|  |     </release> | ||||||
|     <release version="1.5.2" date="2024-05-01" type="stable"> |     <release version="1.5.2" date="2024-05-01" type="stable"> | ||||||
|       <url>https://github.com/Vencord/Vesktop/releases/tag/v1.5.2</url> |       <url>https://github.com/Vencord/Vesktop/releases/tag/v1.5.2</url> | ||||||
|       <description> |       <description> | ||||||
|  | @ -182,7 +210,7 @@ | ||||||
|   <url type="vcs-browser">https://github.com/Vencord/Vesktop</url> |   <url type="vcs-browser">https://github.com/Vencord/Vesktop</url> | ||||||
|   <categories> |   <categories> | ||||||
|     <category>InstantMessaging</category> |     <category>InstantMessaging</category> | ||||||
|     <category>AudioVideo</category> |     <category>Network</category> | ||||||
|   </categories> |   </categories> | ||||||
|   <requires> |   <requires> | ||||||
|     <control>pointing</control> |     <control>pointing</control> | ||||||
|  |  | ||||||
							
								
								
									
										103
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										103
									
								
								package.json
									
									
									
									
									
								
							|  | @ -1,8 +1,8 @@ | ||||||
| { | { | ||||||
|     "name": "vesktop", |     "name": "vesktop", | ||||||
|     "version": "1.5.2", |     "version": "1.5.3", | ||||||
|     "private": true, |     "private": true, | ||||||
|     "description": "", |     "description": "Vesktop is a custom Discord desktop app", | ||||||
|     "keywords": [], |     "keywords": [], | ||||||
|     "homepage": "https://vencord.dev/", |     "homepage": "https://vencord.dev/", | ||||||
|     "license": "GPL-3.0", |     "license": "GPL-3.0", | ||||||
|  | @ -24,38 +24,39 @@ | ||||||
|         "updateMeta": "tsx scripts/utils/updateMeta.mts" |         "updateMeta": "tsx scripts/utils/updateMeta.mts" | ||||||
|     }, |     }, | ||||||
|     "dependencies": { |     "dependencies": { | ||||||
|         "arrpc": "github:OpenAsar/arrpc#6960a8fd4d65d566da93dbdb8a7ca474aa0a3c9c" |         "arrpc": "github:OpenAsar/arrpc#c62ec6a04c8d870530aa6944257fe745f6c59a24", | ||||||
|  |         "electron-updater": "^6.2.1" | ||||||
|     }, |     }, | ||||||
|     "optionalDependencies": { |     "optionalDependencies": { | ||||||
|         "@vencord/venmic": "^3.4.2" |         "@vencord/venmic": "^6.1.0" | ||||||
|     }, |     }, | ||||||
|     "devDependencies": { |     "devDependencies": { | ||||||
|         "@fal-works/esbuild-plugin-global-externals": "^2.1.2", |         "@fal-works/esbuild-plugin-global-externals": "^2.1.2", | ||||||
|         "@types/node": "^20.11.26", |         "@types/node": "^20.14.11", | ||||||
|         "@types/react": "^18.2.65", |         "@types/react": "^18.3.3", | ||||||
|         "@typescript-eslint/eslint-plugin": "^7.2.0", |         "@typescript-eslint/eslint-plugin": "^7.17.0", | ||||||
|         "@typescript-eslint/parser": "^7.2.0", |         "@typescript-eslint/parser": "^7.17.0", | ||||||
|         "@vencord/types": "^0.1.2", |         "@vencord/types": "^1.8.4", | ||||||
|         "dotenv": "^16.4.5", |         "dotenv": "^16.4.5", | ||||||
|         "electron": "^29.1.1", |         "electron": "^31.2.1", | ||||||
|         "electron-builder": "^24.13.3", |         "electron-builder": "^25.0.1", | ||||||
|         "esbuild": "^0.20.1", |         "esbuild": "^0.20.2", | ||||||
|         "eslint": "^8.57.0", |         "eslint": "^8.57.0", | ||||||
|         "eslint-config-prettier": "^9.1.0", |         "eslint-config-prettier": "^9.1.0", | ||||||
|         "eslint-import-resolver-alias": "^1.1.2", |         "eslint-import-resolver-alias": "^1.1.2", | ||||||
|         "eslint-plugin-license-header": "^0.6.0", |         "eslint-plugin-license-header": "^0.6.1", | ||||||
|         "eslint-plugin-path-alias": "^1.0.0", |         "eslint-plugin-path-alias": "^1.1.0", | ||||||
|         "eslint-plugin-prettier": "^5.1.3", |         "eslint-plugin-prettier": "^5.2.1", | ||||||
|         "eslint-plugin-simple-import-sort": "^12.0.0", |         "eslint-plugin-simple-import-sort": "^12.1.1", | ||||||
|         "eslint-plugin-unused-imports": "^3.1.0", |         "eslint-plugin-unused-imports": "^3.2.0", | ||||||
|         "prettier": "^3.2.5", |         "prettier": "^3.3.3", | ||||||
|         "sharp": "^0.33.0", |         "sharp": "^0.33.0", | ||||||
|         "sharp-ico": "^0.1.5", |         "sharp-ico": "^0.1.5", | ||||||
|         "source-map-support": "^0.5.21", |         "source-map-support": "^0.5.21", | ||||||
|         "tsx": "^4.7.1", |         "tsx": "^4.16.2", | ||||||
|         "type-fest": "^4.12.0", |         "type-fest": "^4.23.0", | ||||||
|         "typescript": "^5.4.2", |         "typescript": "^5.5.4", | ||||||
|         "xml-formatter": "^3.6.2" |         "xml-formatter": "^3.6.3" | ||||||
|     }, |     }, | ||||||
|     "packageManager": "pnpm@9.1.0", |     "packageManager": "pnpm@9.1.0", | ||||||
|     "engines": { |     "engines": { | ||||||
|  | @ -67,6 +68,7 @@ | ||||||
|         "productName": "Vesktop", |         "productName": "Vesktop", | ||||||
|         "files": [ |         "files": [ | ||||||
|             "!*", |             "!*", | ||||||
|  |             "!node_modules", | ||||||
|             "dist/js", |             "dist/js", | ||||||
|             "static", |             "static", | ||||||
|             "package.json", |             "package.json", | ||||||
|  | @ -120,38 +122,40 @@ | ||||||
|                 { |                 { | ||||||
|                     "target": "default", |                     "target": "default", | ||||||
|                     "arch": [ |                     "arch": [ | ||||||
|                         "x64", |                         "universal" | ||||||
|                         "arm64" |  | ||||||
|                     ] |                     ] | ||||||
|                 } |                 } | ||||||
|             ], |             ], | ||||||
|             "category": "Network", |             "category": "public.app-category.social-networking", | ||||||
|  |             "darkModeSupport": true, | ||||||
|             "extendInfo": { |             "extendInfo": { | ||||||
|                 "NSMicrophoneUsageDescription": "This app needs access to the microphone", |                 "NSMicrophoneUsageDescription": "This app needs access to the microphone", | ||||||
|                 "NSCameraUsageDescription": "This app needs access to the camera", |                 "NSCameraUsageDescription": "This app needs access to the camera", | ||||||
|                 "com.apple.security.device.audio-input": true, |                 "com.apple.security.device.audio-input": true, | ||||||
|                 "com.apple.security.device.camera": true |                 "com.apple.security.device.camera": true | ||||||
|             } |             }, | ||||||
|  |             "notarize": true | ||||||
|         }, |         }, | ||||||
|         "dmg": { |         "dmg": { | ||||||
|             "background": "build/background.tiff", |             "background": "build/background.tiff", | ||||||
|             "icon": "build/icon.icns", |             "icon": "build/icon.icns", | ||||||
|             "iconSize": 105, |             "iconSize": 105, | ||||||
|             "window": { |             "window": { | ||||||
|               "width": 512, |                 "width": 512, | ||||||
|               "height": 340 |                 "height": 340 | ||||||
|             }, |             }, | ||||||
|        |             "contents": [ | ||||||
|             "contents": [{ |                 { | ||||||
|                 "x": 140, |                     "x": 140, | ||||||
|                 "y": 160 |                     "y": 160 | ||||||
|             }, |                 }, | ||||||
|             { |                 { | ||||||
|                 "x": 372, |                     "x": 372, | ||||||
|                 "y": 160, |                     "y": 160, | ||||||
|                 "type": "link", |                     "type": "link", | ||||||
|                 "path": "/Applications" |                     "path": "/Applications" | ||||||
|             }] |                 } | ||||||
|  |             ] | ||||||
|         }, |         }, | ||||||
|         "nsis": { |         "nsis": { | ||||||
|             "include": "build/installer.nsh", |             "include": "build/installer.nsh", | ||||||
|  | @ -159,12 +163,29 @@ | ||||||
|         }, |         }, | ||||||
|         "win": { |         "win": { | ||||||
|             "target": [ |             "target": [ | ||||||
|                 "nsis", |                 { | ||||||
|                 "zip" |                     "target": "nsis", | ||||||
|  |                     "arch": [ | ||||||
|  |                         "x64", | ||||||
|  |                         "arm64" | ||||||
|  |                     ] | ||||||
|  |                 }, | ||||||
|  |                 { | ||||||
|  |                     "target": "zip", | ||||||
|  |                     "arch": [ | ||||||
|  |                         "x64", | ||||||
|  |                         "arm64" | ||||||
|  |                     ] | ||||||
|  |                 } | ||||||
|             ] |             ] | ||||||
|         }, |         }, | ||||||
|         "publish": { |         "publish": { | ||||||
|             "provider": "github" |             "provider": "github" | ||||||
|         } |         } | ||||||
|  |     }, | ||||||
|  |     "pnpm": { | ||||||
|  |         "patchedDependencies": { | ||||||
|  |             "arrpc@3.4.0": "patches/arrpc@3.4.0.patch" | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
							
								
								
									
										14
									
								
								patches/arrpc@3.4.0.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								patches/arrpc@3.4.0.patch
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | ||||||
|  | diff --git a/src/process/index.js b/src/process/index.js
 | ||||||
|  | index 97ea6514b54dd9c5df588c78f0397d31ab5f882a..c2bdbd6aaa5611bc6ff1d993beeb380b1f5ec575 100644
 | ||||||
|  | --- a/src/process/index.js
 | ||||||
|  | +++ b/src/process/index.js
 | ||||||
|  | @@ -5,8 +5,7 @@ import fs from 'node:fs';
 | ||||||
|  |  import { dirname, join } from 'path'; | ||||||
|  |  import { fileURLToPath } from 'url'; | ||||||
|  |   | ||||||
|  | -const __dirname = dirname(fileURLToPath(import.meta.url));
 | ||||||
|  | -const DetectableDB = JSON.parse(fs.readFileSync(join(__dirname, 'detectable.json'), 'utf8'));
 | ||||||
|  | +const DetectableDB = require('./detectable.json');
 | ||||||
|  |   | ||||||
|  |  import * as Natives from './native/index.js'; | ||||||
|  |  const Native = Natives[process.platform]; | ||||||
							
								
								
									
										7617
									
								
								pnpm-lock.yaml
									
									
									
									
									
								
							
							
						
						
									
										7617
									
								
								pnpm-lock.yaml
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -76,12 +76,6 @@ await Promise.all([ | ||||||
|         outfile: "dist/js/preload.js", |         outfile: "dist/js/preload.js", | ||||||
|         footer: { js: "//# sourceURL=VCDPreload" } |         footer: { js: "//# sourceURL=VCDPreload" } | ||||||
|     }), |     }), | ||||||
|     createContext({ |  | ||||||
|         ...NodeCommonOpts, |  | ||||||
|         entryPoints: ["src/updater/preload.ts"], |  | ||||||
|         outfile: "dist/js/updaterPreload.js", |  | ||||||
|         footer: { js: "//# sourceURL=VCDUpdaterPreload" } |  | ||||||
|     }), |  | ||||||
|     createContext({ |     createContext({ | ||||||
|         ...CommonOpts, |         ...CommonOpts, | ||||||
|         globalName: "Vesktop", |         globalName: "Vesktop", | ||||||
|  |  | ||||||
|  | @ -5,11 +5,22 @@ | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| import { app } from "electron"; | import { app } from "electron"; | ||||||
| import { existsSync, readdirSync, renameSync, rmdirSync } from "fs"; | import { existsSync, mkdirSync, readdirSync, renameSync, rmdirSync } from "fs"; | ||||||
| import { join } from "path"; | import { dirname, join } from "path"; | ||||||
|  | 
 | ||||||
|  | const vesktopDir = dirname(process.execPath); | ||||||
|  | 
 | ||||||
|  | export const PORTABLE = | ||||||
|  |     process.platform === "win32" && | ||||||
|  |     !process.execPath.toLowerCase().endsWith("electron.exe") && | ||||||
|  |     !existsSync(join(vesktopDir, "Uninstall Vesktop.exe")); | ||||||
| 
 | 
 | ||||||
| const LEGACY_DATA_DIR = join(app.getPath("appData"), "VencordDesktop", "VencordDesktop"); | const LEGACY_DATA_DIR = join(app.getPath("appData"), "VencordDesktop", "VencordDesktop"); | ||||||
| export const DATA_DIR = process.env.VENCORD_USER_DATA_DIR || join(app.getPath("userData")); | export const DATA_DIR = | ||||||
|  |     process.env.VENCORD_USER_DATA_DIR || (PORTABLE ? join(vesktopDir, "Data") : join(app.getPath("userData"))); | ||||||
|  | 
 | ||||||
|  | mkdirSync(DATA_DIR, { recursive: true }); | ||||||
|  | 
 | ||||||
| // TODO: remove eventually
 | // TODO: remove eventually
 | ||||||
| if (existsSync(LEGACY_DATA_DIR)) { | if (existsSync(LEGACY_DATA_DIR)) { | ||||||
|     try { |     try { | ||||||
|  | @ -26,7 +37,8 @@ if (existsSync(LEGACY_DATA_DIR)) { | ||||||
|         console.error("Migration failed", e); |         console.error("Migration failed", e); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| app.setPath("sessionData", join(DATA_DIR, "sessionData")); | const SESSION_DATA_DIR = join(DATA_DIR, "sessionData"); | ||||||
|  | app.setPath("sessionData", SESSION_DATA_DIR); | ||||||
| 
 | 
 | ||||||
| export const VENCORD_SETTINGS_DIR = join(DATA_DIR, "settings"); | export const VENCORD_SETTINGS_DIR = join(DATA_DIR, "settings"); | ||||||
| export const VENCORD_QUICKCSS_FILE = join(VENCORD_SETTINGS_DIR, "quickCss.css"); | export const VENCORD_QUICKCSS_FILE = join(VENCORD_SETTINGS_DIR, "quickCss.css"); | ||||||
|  | @ -36,7 +48,8 @@ export const VENCORD_THEMES_DIR = join(DATA_DIR, "themes"); | ||||||
| // needs to be inline require because of circular dependency
 | // needs to be inline require because of circular dependency
 | ||||||
| // as otherwise "DATA_DIR" (which is used by ./settings) will be uninitialised
 | // as otherwise "DATA_DIR" (which is used by ./settings) will be uninitialised
 | ||||||
| export const VENCORD_FILES_DIR = | export const VENCORD_FILES_DIR = | ||||||
|     (require("./settings") as typeof import("./settings")).Settings.store.vencordDir || join(DATA_DIR, "vencordDist"); |     (require("./settings") as typeof import("./settings")).State.store.vencordDir || | ||||||
|  |     join(SESSION_DATA_DIR, "vencordFiles"); | ||||||
| 
 | 
 | ||||||
| export const USER_AGENT = `Vesktop/${app.getVersion()} (https://github.com/Vencord/Vesktop)`; | export const USER_AGENT = `Vesktop/${app.getVersion()} (https://github.com/Vencord/Vesktop)`; | ||||||
| 
 | 
 | ||||||
|  | @ -49,10 +62,10 @@ export const DEFAULT_HEIGHT = 720; | ||||||
| export const DISCORD_HOSTNAMES = ["discord.com", "canary.discord.com", "ptb.discord.com"]; | export const DISCORD_HOSTNAMES = ["discord.com", "canary.discord.com", "ptb.discord.com"]; | ||||||
| 
 | 
 | ||||||
| const BrowserUserAgents = { | const BrowserUserAgents = { | ||||||
|     darwin: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", |     darwin: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36", | ||||||
|     linux: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", |     linux: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36", | ||||||
|     windows: |     windows: | ||||||
|         "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36" |         "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36" | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export const BrowserUserAgent = BrowserUserAgents[process.platform] || BrowserUserAgents.windows; | export const BrowserUserAgent = BrowserUserAgents[process.platform] || BrowserUserAgents.windows; | ||||||
|  |  | ||||||
|  | @ -18,11 +18,11 @@ import { Settings, State } from "./settings"; | ||||||
| import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally"; | import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally"; | ||||||
| 
 | 
 | ||||||
| interface Data { | interface Data { | ||||||
|     minimizeToTray: boolean; |  | ||||||
|     discordBranch: "stable" | "canary" | "ptb"; |     discordBranch: "stable" | "canary" | "ptb"; | ||||||
|     autoStart: boolean; |     minimizeToTray?: "on"; | ||||||
|     importSettings: boolean; |     autoStart?: "on"; | ||||||
|     richPresence: boolean; |     importSettings?: "on"; | ||||||
|  |     richPresence?: "on"; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function createFirstLaunchTour() { | export function createFirstLaunchTour() { | ||||||
|  | @ -44,10 +44,11 @@ export function createFirstLaunchTour() { | ||||||
|         if (!msg.startsWith("form:")) return; |         if (!msg.startsWith("form:")) return; | ||||||
|         const data = JSON.parse(msg.slice(5)) as Data; |         const data = JSON.parse(msg.slice(5)) as Data; | ||||||
| 
 | 
 | ||||||
|  |         console.log(data); | ||||||
|         State.store.firstLaunch = false; |         State.store.firstLaunch = false; | ||||||
|         Settings.store.minimizeToTray = data.minimizeToTray; |  | ||||||
|         Settings.store.discordBranch = data.discordBranch; |         Settings.store.discordBranch = data.discordBranch; | ||||||
|         Settings.store.arRPC = data.richPresence; |         Settings.store.minimizeToTray = !!data.minimizeToTray; | ||||||
|  |         Settings.store.arRPC = !!data.richPresence; | ||||||
| 
 | 
 | ||||||
|         if (data.autoStart) autoStart.enable(); |         if (data.autoStart) autoStart.enable(); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -7,7 +7,7 @@ | ||||||
| import "./ipc"; | import "./ipc"; | ||||||
| 
 | 
 | ||||||
| import { app, BrowserWindow, nativeTheme } from "electron"; | import { app, BrowserWindow, nativeTheme } from "electron"; | ||||||
| import { checkUpdates } from "updater/main"; | import { autoUpdater } from "electron-updater"; | ||||||
| 
 | 
 | ||||||
| import { DATA_DIR } from "./constants"; | import { DATA_DIR } from "./constants"; | ||||||
| import { createFirstLaunchTour } from "./firstLaunch"; | import { createFirstLaunchTour } from "./firstLaunch"; | ||||||
|  | @ -19,6 +19,8 @@ import { isDeckGameMode } from "./utils/steamOS"; | ||||||
| 
 | 
 | ||||||
| if (IS_DEV) { | if (IS_DEV) { | ||||||
|     require("source-map-support").install(); |     require("source-map-support").install(); | ||||||
|  | } else { | ||||||
|  |     autoUpdater.checkForUpdatesAndNotify(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Make the Vencord files use our DATA_DIR
 | // Make the Vencord files use our DATA_DIR
 | ||||||
|  | @ -40,18 +42,23 @@ function init() { | ||||||
|         app.commandLine.appendSwitch("disable-smooth-scrolling"); |         app.commandLine.appendSwitch("disable-smooth-scrolling"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     // disable renderer backgrounding to prevent the app from unloading when in the background
 | ||||||
|  |     // https://github.com/electron/electron/issues/2822
 | ||||||
|  |     // https://github.com/GoogleChrome/chrome-launcher/blob/5a27dd574d47a75fec0fb50f7b774ebf8a9791ba/docs/chrome-flags-for-tools.md#task-throttling
 | ||||||
|  |     app.commandLine.appendSwitch("disable-renderer-backgrounding"); | ||||||
|  |     app.commandLine.appendSwitch("disable-background-timer-throttling"); | ||||||
|  |     app.commandLine.appendSwitch("disable-backgrounding-occluded-windows"); | ||||||
|  |     if (process.platform === "win32") { | ||||||
|  |         disabledFeatures.push("CalculateNativeWinOcclusion"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     // work around chrome 66 disabling autoplay by default
 |     // work around chrome 66 disabling autoplay by default
 | ||||||
|     app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required"); |     app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required"); | ||||||
|     // WinRetrieveSuggestionsOnlyOnDemand: Work around electron 13 bug w/ async spellchecking on Windows.
 |     // WinRetrieveSuggestionsOnlyOnDemand: Work around electron 13 bug w/ async spellchecking on Windows.
 | ||||||
|     // HardwareMediaKeyHandling,MediaSessionService: Prevent Discord from registering as a media service.
 |     // HardwareMediaKeyHandling,MediaSessionService: Prevent Discord from registering as a media service.
 | ||||||
|     //
 |     //
 | ||||||
|     // WidgetLayering (Vencord Added): Fix DevTools context menus https://github.com/electron/electron/issues/38790
 |     // WidgetLayering (Vencord Added): Fix DevTools context menus https://github.com/electron/electron/issues/38790
 | ||||||
|     disabledFeatures.push( |     disabledFeatures.push("WinRetrieveSuggestionsOnlyOnDemand", "HardwareMediaKeyHandling", "MediaSessionService"); | ||||||
|         "WinRetrieveSuggestionsOnlyOnDemand", |  | ||||||
|         "HardwareMediaKeyHandling", |  | ||||||
|         "MediaSessionService", |  | ||||||
|         "WidgetLayering" |  | ||||||
|     ); |  | ||||||
| 
 | 
 | ||||||
|     app.commandLine.appendSwitch("enable-features", [...new Set(enabledFeatures)].filter(Boolean).join(",")); |     app.commandLine.appendSwitch("enable-features", [...new Set(enabledFeatures)].filter(Boolean).join(",")); | ||||||
|     app.commandLine.appendSwitch("disable-features", [...new Set(disabledFeatures)].filter(Boolean).join(",")); |     app.commandLine.appendSwitch("disable-features", [...new Set(disabledFeatures)].filter(Boolean).join(",")); | ||||||
|  | @ -69,7 +76,6 @@ function init() { | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     app.whenReady().then(async () => { |     app.whenReady().then(async () => { | ||||||
|         checkUpdates(); |  | ||||||
|         if (process.platform === "win32") app.setAppUserModelId("dev.vencord.vesktop"); |         if (process.platform === "win32") app.setAppUserModelId("dev.vencord.vesktop"); | ||||||
| 
 | 
 | ||||||
|         registerScreenShareHandler(); |         registerScreenShareHandler(); | ||||||
|  |  | ||||||
|  | @ -19,7 +19,7 @@ import { IpcEvents } from "../shared/IpcEvents"; | ||||||
| import { autoStart } from "./autoStart"; | import { autoStart } from "./autoStart"; | ||||||
| import { VENCORD_FILES_DIR, VENCORD_QUICKCSS_FILE, VENCORD_THEMES_DIR } from "./constants"; | import { VENCORD_FILES_DIR, VENCORD_QUICKCSS_FILE, VENCORD_THEMES_DIR } from "./constants"; | ||||||
| import { mainWin } from "./mainWindow"; | import { mainWin } from "./mainWindow"; | ||||||
| import { Settings } from "./settings"; | import { Settings, State } from "./settings"; | ||||||
| import { setBadgeCount } from "./appBadge"; | import { setBadgeCount } from "./appBadge"; | ||||||
| import { handle, handleSync } from "./utils/ipcWrappers"; | import { handle, handleSync } from "./utils/ipcWrappers"; | ||||||
| import { PopoutWindows } from "./utils/popout"; | import { PopoutWindows } from "./utils/popout"; | ||||||
|  | @ -94,12 +94,8 @@ handle(IpcEvents.MAXIMIZE, e => { | ||||||
|     } |     } | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| handle(IpcEvents.SPELLCHECK_SET_LANGUAGES, (_, languages: string[]) => { | handleSync(IpcEvents.SPELLCHECK_GET_AVAILABLE_LANGUAGES, e => { | ||||||
|     const ses = session.defaultSession; |     e.returnValue = session.defaultSession.availableSpellCheckerLanguages; | ||||||
| 
 |  | ||||||
|     const available = ses.availableSpellCheckerLanguages; |  | ||||||
|     const applicable = languages.filter(l => available.includes(l)).slice(0, 3); |  | ||||||
|     if (applicable.length) ses.setSpellCheckerLanguages(applicable); |  | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| handle(IpcEvents.SPELLCHECK_REPLACE_MISSPELLING, (e, word: string) => { | handle(IpcEvents.SPELLCHECK_REPLACE_MISSPELLING, (e, word: string) => { | ||||||
|  | @ -110,7 +106,14 @@ handle(IpcEvents.SPELLCHECK_ADD_TO_DICTIONARY, (e, word: string) => { | ||||||
|     e.sender.session.addWordToSpellCheckerDictionary(word); |     e.sender.session.addWordToSpellCheckerDictionary(word); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| handle(IpcEvents.SELECT_VENCORD_DIR, async () => { | handleSync(IpcEvents.GET_VENCORD_DIR, e => (e.returnValue = State.store.vencordDir)); | ||||||
|  | 
 | ||||||
|  | handle(IpcEvents.SELECT_VENCORD_DIR, async (_e, value?: null) => { | ||||||
|  |     if (value === null) { | ||||||
|  |         delete State.store.vencordDir; | ||||||
|  |         return "ok"; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     const res = await dialog.showOpenDialog(mainWin!, { |     const res = await dialog.showOpenDialog(mainWin!, { | ||||||
|         properties: ["openDirectory"] |         properties: ["openDirectory"] | ||||||
|     }); |     }); | ||||||
|  | @ -119,7 +122,9 @@ handle(IpcEvents.SELECT_VENCORD_DIR, async () => { | ||||||
|     const dir = res.filePaths[0]; |     const dir = res.filePaths[0]; | ||||||
|     if (!isValidVencordInstall(dir)) return "invalid"; |     if (!isValidVencordInstall(dir)) return "invalid"; | ||||||
| 
 | 
 | ||||||
|     return dir; |     State.store.vencordDir = dir; | ||||||
|  | 
 | ||||||
|  |     return "ok"; | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| handle(IpcEvents.SET_BADGE_COUNT, (_, count: number) => setBadgeCount(count)); | handle(IpcEvents.SET_BADGE_COUNT, (_, count: number) => setBadgeCount(count)); | ||||||
|  |  | ||||||
|  | @ -13,6 +13,7 @@ import { | ||||||
|     MenuItemConstructorOptions, |     MenuItemConstructorOptions, | ||||||
|     nativeTheme, |     nativeTheme, | ||||||
|     screen, |     screen, | ||||||
|  |     session, | ||||||
|     Tray |     Tray | ||||||
| } from "electron"; | } from "electron"; | ||||||
| import { rm } from "fs/promises"; | import { rm } from "fs/promises"; | ||||||
|  | @ -90,7 +91,7 @@ function initTray(win: BrowserWindow) { | ||||||
|             click: createAboutWindow |             click: createAboutWindow | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             label: "Update Vencord", |             label: "Repair Vencord", | ||||||
|             async click() { |             async click() { | ||||||
|                 await downloadVencordFiles(); |                 await downloadVencordFiles(); | ||||||
|                 app.relaunch(); |                 app.relaunch(); | ||||||
|  | @ -107,14 +108,14 @@ function initTray(win: BrowserWindow) { | ||||||
|             type: "separator" |             type: "separator" | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             label: "Relaunch", |             label: "Restart", | ||||||
|             click() { |             click() { | ||||||
|                 app.relaunch(); |                 app.relaunch(); | ||||||
|                 app.quit(); |                 app.quit(); | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             label: "Quit Vesktop", |             label: "Quit", | ||||||
|             click() { |             click() { | ||||||
|                 isQuitting = true; |                 isQuitting = true; | ||||||
|                 app.quit(); |                 app.quit(); | ||||||
|  | @ -364,12 +365,27 @@ function initSettingsListeners(win: BrowserWindow) { | ||||||
|     addSettingsListener("enableMenu", enabled => { |     addSettingsListener("enableMenu", enabled => { | ||||||
|         win.setAutoHideMenuBar(enabled ?? false); |         win.setAutoHideMenuBar(enabled ?? false); | ||||||
|     }); |     }); | ||||||
|  | 
 | ||||||
|  |     addSettingsListener("spellCheckLanguages", languages => initSpellCheckLanguages(win, languages)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function initSpellCheckLanguages(win: BrowserWindow, languages?: string[]) { | ||||||
|  |     languages ??= await win.webContents.executeJavaScript("[...new Set(navigator.languages)]").catch(() => []); | ||||||
|  |     if (!languages) return; | ||||||
|  | 
 | ||||||
|  |     const ses = session.defaultSession; | ||||||
|  | 
 | ||||||
|  |     const available = ses.availableSpellCheckerLanguages; | ||||||
|  |     const applicable = languages.filter(l => available.includes(l)).slice(0, 5); | ||||||
|  |     if (applicable.length) ses.setSpellCheckerLanguages(applicable); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function initSpellCheck(win: BrowserWindow) { | function initSpellCheck(win: BrowserWindow) { | ||||||
|     win.webContents.on("context-menu", (_, data) => { |     win.webContents.on("context-menu", (_, data) => { | ||||||
|         win.webContents.send(IpcEvents.SPELLCHECK_RESULT, data.misspelledWord, data.dictionarySuggestions); |         win.webContents.send(IpcEvents.SPELLCHECK_RESULT, data.misspelledWord, data.dictionarySuggestions); | ||||||
|     }); |     }); | ||||||
|  | 
 | ||||||
|  |     initSpellCheckLanguages(win, Settings.store.spellCheckLanguages); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function createMainWindow() { | function createMainWindow() { | ||||||
|  | @ -391,7 +407,9 @@ function createMainWindow() { | ||||||
|             contextIsolation: true, |             contextIsolation: true, | ||||||
|             devTools: true, |             devTools: true, | ||||||
|             preload: join(__dirname, "preload.js"), |             preload: join(__dirname, "preload.js"), | ||||||
|             spellcheck: true |             spellcheck: true, | ||||||
|  |             // disable renderer backgrounding to prevent the app from unloading when in the background
 | ||||||
|  |             backgroundThrottling: false | ||||||
|         }, |         }, | ||||||
|         icon: ICON_PATH, |         icon: ICON_PATH, | ||||||
|         frame: !noFrame, |         frame: !noFrame, | ||||||
|  | @ -416,6 +434,7 @@ function createMainWindow() { | ||||||
|         autoHideMenuBar: enableMenu |         autoHideMenuBar: enableMenu | ||||||
|     })); |     })); | ||||||
|     win.setMenuBarVisibility(false); |     win.setMenuBarVisibility(false); | ||||||
|  |     if (process.platform === "darwin" && customTitleBar) win.setWindowButtonVisibility(false); | ||||||
| 
 | 
 | ||||||
|     win.on("close", e => { |     win.on("close", e => { | ||||||
|         const useTray = !isDeckGameMode && Settings.store.minimizeToTray !== false && Settings.store.tray !== false; |         const useTray = !isDeckGameMode && Settings.store.minimizeToTray !== false && Settings.store.tray !== false; | ||||||
|  |  | ||||||
|  | @ -12,11 +12,13 @@ export function registerMediaPermissionsHandler() { | ||||||
|     session.defaultSession.setPermissionRequestHandler(async (_webContents, permission, callback, details) => { |     session.defaultSession.setPermissionRequestHandler(async (_webContents, permission, callback, details) => { | ||||||
|         let granted = true; |         let granted = true; | ||||||
| 
 | 
 | ||||||
|         if (details.mediaTypes?.includes("audio")) { |         if ("mediaTypes" in details) { | ||||||
|             granted = await systemPreferences.askForMediaAccess("microphone"); |             if (details.mediaTypes?.includes("audio")) { | ||||||
|         } |                 granted &&= await systemPreferences.askForMediaAccess("microphone"); | ||||||
|         if (details.mediaTypes?.includes("video")) { |             } | ||||||
|             granted &&= await systemPreferences.askForMediaAccess("camera"); |             if (details.mediaTypes?.includes("video")) { | ||||||
|  |                 granted &&= await systemPreferences.askForMediaAccess("camera"); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         callback(granted); |         callback(granted); | ||||||
|  |  | ||||||
|  | @ -35,25 +35,13 @@ function loadSettings<T extends object = any>(file: string, name: string) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const Settings = loadSettings<TSettings>(SETTINGS_FILE, "Vesktop settings"); | export const Settings = loadSettings<TSettings>(SETTINGS_FILE, "Vesktop settings"); | ||||||
| if (Object.hasOwn(Settings.plain, "discordWindowsTitleBar")) { |  | ||||||
|     Settings.plain.customTitleBar = Settings.plain.discordWindowsTitleBar; |  | ||||||
|     delete Settings.plain.discordWindowsTitleBar; |  | ||||||
|     Settings.markAsChanged(); |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| export const VencordSettings = loadSettings<any>(VENCORD_SETTINGS_FILE, "Vencord settings"); | export const VencordSettings = loadSettings<any>(VENCORD_SETTINGS_FILE, "Vencord settings"); | ||||||
| 
 | 
 | ||||||
| if (Object.hasOwn(Settings.plain, "firstLaunch") && !existsSync(STATE_FILE)) { | if (Object.hasOwn(Settings.plain, "firstLaunch") && !existsSync(STATE_FILE)) { | ||||||
|     console.warn("legacy state in settings.json detected. migrating to state.json"); |     console.warn("legacy state in settings.json detected. migrating to state.json"); | ||||||
|     const state = {} as TState; |     const state = {} as TState; | ||||||
|     for (const prop of [ |     for (const prop of ["firstLaunch", "maximized", "minimized", "steamOSLayoutVersion", "windowBounds"] as const) { | ||||||
|         "firstLaunch", |  | ||||||
|         "maximized", |  | ||||||
|         "minimized", |  | ||||||
|         "skippedUpdate", |  | ||||||
|         "steamOSLayoutVersion", |  | ||||||
|         "windowBounds" |  | ||||||
|     ] as const) { |  | ||||||
|         state[prop] = Settings.plain[prop]; |         state[prop] = Settings.plain[prop]; | ||||||
|         delete Settings.plain[prop]; |         delete Settings.plain[prop]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -4,7 +4,8 @@ | ||||||
|  * Copyright (c) 2023 Vendicated and Vencord contributors |  * Copyright (c) 2023 Vendicated and Vencord contributors | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| import { existsSync, mkdirSync } from "fs"; | import { mkdirSync } from "fs"; | ||||||
|  | import { access, constants as FsConstants } from "fs/promises"; | ||||||
| import { join } from "path"; | import { join } from "path"; | ||||||
| 
 | 
 | ||||||
| import { USER_AGENT, VENCORD_FILES_DIR } from "../constants"; | import { USER_AGENT, VENCORD_FILES_DIR } from "../constants"; | ||||||
|  | @ -56,12 +57,18 @@ export async function downloadVencordFiles() { | ||||||
|     ); |     ); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function isValidVencordInstall(dir: string) { | const existsAsync = (path: string) => | ||||||
|     return FILES_TO_DOWNLOAD.every(f => existsSync(join(dir, f))); |     access(path, FsConstants.F_OK) | ||||||
|  |         .then(() => true) | ||||||
|  |         .catch(() => false); | ||||||
|  | 
 | ||||||
|  | export async function isValidVencordInstall(dir: string) { | ||||||
|  |     return Promise.all(FILES_TO_DOWNLOAD.map(f => existsAsync(join(dir, f)))).then(arr => !arr.includes(false)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function ensureVencordFiles() { | export async function ensureVencordFiles() { | ||||||
|     if (isValidVencordInstall(VENCORD_FILES_DIR)) return; |     if (await isValidVencordInstall(VENCORD_FILES_DIR)) return; | ||||||
|  | 
 | ||||||
|     mkdirSync(VENCORD_FILES_DIR, { recursive: true }); |     mkdirSync(VENCORD_FILES_DIR, { recursive: true }); | ||||||
| 
 | 
 | ||||||
|     await downloadVencordFiles(); |     await downloadVencordFiles(); | ||||||
|  |  | ||||||
|  | @ -4,13 +4,13 @@ | ||||||
|  * Copyright (c) 2023 Vendicated and Vencord contributors |  * Copyright (c) 2023 Vendicated and Vencord contributors | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| import type { PatchBay as PatchBayType } from "@vencord/venmic"; | import type { LinkData, Node, PatchBay as PatchBayType } from "@vencord/venmic"; | ||||||
| import { app, ipcMain } from "electron"; | import { app, ipcMain } from "electron"; | ||||||
| import { join } from "path"; | import { join } from "path"; | ||||||
| import { IpcEvents } from "shared/IpcEvents"; | import { IpcEvents } from "shared/IpcEvents"; | ||||||
| import { STATIC_DIR } from "shared/paths"; | import { STATIC_DIR } from "shared/paths"; | ||||||
| 
 | 
 | ||||||
| type LinkData = Parameters<PatchBayType["link"]>[0]; | import { Settings } from "./settings"; | ||||||
| 
 | 
 | ||||||
| let PatchBay: typeof PatchBayType | undefined; | let PatchBay: typeof PatchBayType | undefined; | ||||||
| let patchBayInstance: PatchBayType | undefined; | let patchBayInstance: PatchBayType | undefined; | ||||||
|  | @ -69,47 +69,64 @@ function getRendererAudioServicePid() { | ||||||
| ipcMain.handle(IpcEvents.VIRT_MIC_LIST, () => { | ipcMain.handle(IpcEvents.VIRT_MIC_LIST, () => { | ||||||
|     const audioPid = getRendererAudioServicePid(); |     const audioPid = getRendererAudioServicePid(); | ||||||
| 
 | 
 | ||||||
|     const list = obtainVenmic() |     const { granularSelect } = Settings.store.audio ?? {}; | ||||||
|         ?.list() |  | ||||||
|         .filter(s => s["application.process.id"] !== audioPid) |  | ||||||
|         .map(s => s["application.name"]); |  | ||||||
| 
 | 
 | ||||||
|     const uniqueTargets = [...new Set(list)]; |     const targets = obtainVenmic() | ||||||
|  |         ?.list(granularSelect ? ["node.name"] : undefined) | ||||||
|  |         .filter(s => s["application.process.id"] !== audioPid); | ||||||
| 
 | 
 | ||||||
|     return list ? { ok: true, targets: uniqueTargets, hasPipewirePulse } : { ok: false, isGlibCxxOutdated }; |     return targets ? { ok: true, targets, hasPipewirePulse } : { ok: false, isGlibCxxOutdated }; | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| ipcMain.handle(IpcEvents.VIRT_MIC_START, (_, targets: string[], workaround?: boolean) => { | ipcMain.handle(IpcEvents.VIRT_MIC_START, (_, include: Node[]) => { | ||||||
|     const pid = getRendererAudioServicePid(); |     const pid = getRendererAudioServicePid(); | ||||||
|  |     const { ignoreDevices, ignoreInputMedia, ignoreVirtual, workaround } = Settings.store.audio ?? {}; | ||||||
| 
 | 
 | ||||||
|     const data: LinkData = { |     const data: LinkData = { | ||||||
|         include: targets.map(target => ({ key: "application.name", value: target })), |         include, | ||||||
|         exclude: [{ key: "application.process.id", value: pid }] |         exclude: [{ "application.process.id": pid }], | ||||||
|  |         ignore_devices: ignoreDevices | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|  |     if (ignoreInputMedia ?? true) { | ||||||
|  |         data.exclude.push({ "media.class": "Stream/Input/Audio" }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (ignoreVirtual) { | ||||||
|  |         data.exclude.push({ "node.virtual": "true" }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     if (workaround) { |     if (workaround) { | ||||||
|         data.workaround = [ |         data.workaround = [{ "application.process.id": pid, "media.name": "RecordStream" }]; | ||||||
|             { key: "application.process.id", value: pid }, |  | ||||||
|             { key: "media.name", value: "RecordStream" } |  | ||||||
|         ]; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return obtainVenmic()?.link(data); |     return obtainVenmic()?.link(data); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| ipcMain.handle(IpcEvents.VIRT_MIC_START_SYSTEM, (_, workaround?: boolean, onlyDefaultSpeakers?: boolean) => { | ipcMain.handle(IpcEvents.VIRT_MIC_START_SYSTEM, (_, exclude: Node[]) => { | ||||||
|     const pid = getRendererAudioServicePid(); |     const pid = getRendererAudioServicePid(); | ||||||
| 
 | 
 | ||||||
|  |     const { workaround, ignoreDevices, ignoreInputMedia, ignoreVirtual, onlySpeakers, onlyDefaultSpeakers } = | ||||||
|  |         Settings.store.audio ?? {}; | ||||||
|  | 
 | ||||||
|     const data: LinkData = { |     const data: LinkData = { | ||||||
|         exclude: [{ key: "application.process.id", value: pid }], |         include: [], | ||||||
|  |         exclude: [{ "application.process.id": pid }, ...exclude], | ||||||
|  |         only_speakers: onlySpeakers, | ||||||
|  |         ignore_devices: ignoreDevices, | ||||||
|         only_default_speakers: onlyDefaultSpeakers |         only_default_speakers: onlyDefaultSpeakers | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|  |     if (ignoreInputMedia ?? true) { | ||||||
|  |         data.exclude.push({ "media.class": "Stream/Input/Audio" }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (ignoreVirtual) { | ||||||
|  |         data.exclude.push({ "node.virtual": "true" }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     if (workaround) { |     if (workaround) { | ||||||
|         data.workaround = [ |         data.workaround = [{ "application.process.id": pid, "media.name": "RecordStream" }]; | ||||||
|             { key: "application.process.id", value: pid }, |  | ||||||
|             { key: "media.name", value: "RecordStream" } |  | ||||||
|         ]; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return obtainVenmic()?.link(data); |     return obtainVenmic()?.link(data); | ||||||
|  |  | ||||||
|  | @ -4,9 +4,9 @@ | ||||||
|  * Copyright (c) 2023 Vendicated and Vencord contributors |  * Copyright (c) 2023 Vendicated and Vencord contributors | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
|  | import { Node } from "@vencord/venmic"; | ||||||
| import { ipcRenderer } from "electron"; | import { ipcRenderer } from "electron"; | ||||||
| import type { Settings } from "shared/settings"; | import type { Settings } from "shared/settings"; | ||||||
| import type { LiteralUnion } from "type-fest"; |  | ||||||
| 
 | 
 | ||||||
| import { IpcEvents } from "../shared/IpcEvents"; | import { IpcEvents } from "../shared/IpcEvents"; | ||||||
| import { invoke, sendSync } from "./typedIpc"; | import { invoke, sendSync } from "./typedIpc"; | ||||||
|  | @ -33,14 +33,15 @@ export const VesktopNative = { | ||||||
|     }, |     }, | ||||||
|     fileManager: { |     fileManager: { | ||||||
|         showItemInFolder: (path: string) => invoke<void>(IpcEvents.SHOW_ITEM_IN_FOLDER, path), |         showItemInFolder: (path: string) => invoke<void>(IpcEvents.SHOW_ITEM_IN_FOLDER, path), | ||||||
|         selectVencordDir: () => invoke<LiteralUnion<"cancelled" | "invalid", string>>(IpcEvents.SELECT_VENCORD_DIR) |         getVencordDir: () => sendSync<string | undefined>(IpcEvents.GET_VENCORD_DIR), | ||||||
|  |         selectVencordDir: (value?: null) => invoke<"cancelled" | "invalid" | "ok">(IpcEvents.SELECT_VENCORD_DIR, value) | ||||||
|     }, |     }, | ||||||
|     settings: { |     settings: { | ||||||
|         get: () => sendSync<Settings>(IpcEvents.GET_SETTINGS), |         get: () => sendSync<Settings>(IpcEvents.GET_SETTINGS), | ||||||
|         set: (settings: Settings, path?: string) => invoke<void>(IpcEvents.SET_SETTINGS, settings, path) |         set: (settings: Settings, path?: string) => invoke<void>(IpcEvents.SET_SETTINGS, settings, path) | ||||||
|     }, |     }, | ||||||
|     spellcheck: { |     spellcheck: { | ||||||
|         setLanguages: (languages: readonly string[]) => invoke<void>(IpcEvents.SPELLCHECK_SET_LANGUAGES, languages), |         getAvailableLanguages: () => sendSync<string[]>(IpcEvents.SPELLCHECK_GET_AVAILABLE_LANGUAGES), | ||||||
|         onSpellcheckResult(cb: SpellCheckerResultCallback) { |         onSpellcheckResult(cb: SpellCheckerResultCallback) { | ||||||
|             spellCheckCallbacks.add(cb); |             spellCheckCallbacks.add(cb); | ||||||
|         }, |         }, | ||||||
|  | @ -63,11 +64,10 @@ export const VesktopNative = { | ||||||
|     virtmic: { |     virtmic: { | ||||||
|         list: () => |         list: () => | ||||||
|             invoke< |             invoke< | ||||||
|                 { ok: false; isGlibCxxOutdated: boolean } | { ok: true; targets: string[]; hasPipewirePulse: boolean } |                 { ok: false; isGlibCxxOutdated: boolean } | { ok: true; targets: Node[]; hasPipewirePulse: boolean } | ||||||
|             >(IpcEvents.VIRT_MIC_LIST), |             >(IpcEvents.VIRT_MIC_LIST), | ||||||
|         start: (targets: string[], workaround?: boolean) => invoke<void>(IpcEvents.VIRT_MIC_START, targets, workaround), |         start: (include: Node[]) => invoke<void>(IpcEvents.VIRT_MIC_START, include), | ||||||
|         startSystem: (workaround?: boolean, onlyDefaultSpeakers?: boolean) => |         startSystem: (exclude: Node[]) => invoke<void>(IpcEvents.VIRT_MIC_START_SYSTEM, exclude), | ||||||
|             invoke<void>(IpcEvents.VIRT_MIC_START_SYSTEM, workaround, onlyDefaultSpeakers), |  | ||||||
|         stop: () => invoke<void>(IpcEvents.VIRT_MIC_STOP) |         stop: () => invoke<void>(IpcEvents.VIRT_MIC_STOP) | ||||||
|     }, |     }, | ||||||
|     arrpc: { |     arrpc: { | ||||||
|  |  | ||||||
|  | @ -40,5 +40,3 @@ if (IS_DEV) { | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
| // #endregion
 | // #endregion
 | ||||||
| 
 |  | ||||||
| VesktopNative.spellcheck.setLanguages(window.navigator.languages); |  | ||||||
|  |  | ||||||
|  | @ -6,7 +6,7 @@ | ||||||
| 
 | 
 | ||||||
| import "./screenSharePicker.css"; | import "./screenSharePicker.css"; | ||||||
| 
 | 
 | ||||||
| import { closeModal, Logger, Margins, Modals, ModalSize, openModal, useAwaiter } from "@vencord/types/utils"; | import { closeModal, Logger, Modals, ModalSize, openModal, useAwaiter } from "@vencord/types/utils"; | ||||||
| import { findStoreLazy, onceReady } from "@vencord/types/webpack"; | import { findStoreLazy, onceReady } from "@vencord/types/webpack"; | ||||||
| import { | import { | ||||||
|     Button, |     Button, | ||||||
|  | @ -19,8 +19,10 @@ import { | ||||||
|     UserStore, |     UserStore, | ||||||
|     useState |     useState | ||||||
| } from "@vencord/types/webpack/common"; | } from "@vencord/types/webpack/common"; | ||||||
|  | import { Node } from "@vencord/venmic"; | ||||||
| import type { Dispatch, SetStateAction } from "react"; | import type { Dispatch, SetStateAction } from "react"; | ||||||
| import { addPatch } from "renderer/patches/shared"; | import { addPatch } from "renderer/patches/shared"; | ||||||
|  | import { useSettings } from "renderer/settings"; | ||||||
| import { isLinux, isWindows } from "renderer/utils"; | import { isLinux, isWindows } from "renderer/utils"; | ||||||
| 
 | 
 | ||||||
| const StreamResolutions = ["480", "720", "1080", "1440"] as const; | const StreamResolutions = ["480", "720", "1080", "1440"] as const; | ||||||
|  | @ -31,14 +33,23 @@ const MediaEngineStore = findStoreLazy("MediaEngineStore"); | ||||||
| export type StreamResolution = (typeof StreamResolutions)[number]; | export type StreamResolution = (typeof StreamResolutions)[number]; | ||||||
| export type StreamFps = (typeof StreamFps)[number]; | export type StreamFps = (typeof StreamFps)[number]; | ||||||
| 
 | 
 | ||||||
|  | type SpecialSource = "None" | "Entire System"; | ||||||
|  | 
 | ||||||
|  | type AudioSource = SpecialSource | Node; | ||||||
|  | type AudioSources = SpecialSource | Node[]; | ||||||
|  | 
 | ||||||
|  | interface AudioItem { | ||||||
|  |     name: string; | ||||||
|  |     value: AudioSource; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| interface StreamSettings { | interface StreamSettings { | ||||||
|     resolution: StreamResolution; |     resolution: StreamResolution; | ||||||
|     fps: StreamFps; |     fps: StreamFps; | ||||||
|     audio: boolean; |     audio: boolean; | ||||||
|     audioSource?: string; |  | ||||||
|     contentHint?: string; |     contentHint?: string; | ||||||
|     workaround?: boolean; |     includeSources?: AudioSources; | ||||||
|     onlyDefaultSpeakers?: boolean; |     excludeSources?: AudioSources; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export interface StreamPick extends StreamSettings { | export interface StreamPick extends StreamSettings { | ||||||
|  | @ -118,13 +129,17 @@ export function openScreenSharePicker(screens: Source[], skipPicker: boolean) { | ||||||
|                     modalProps={props} |                     modalProps={props} | ||||||
|                     submit={async v => { |                     submit={async v => { | ||||||
|                         didSubmit = true; |                         didSubmit = true; | ||||||
|                         if (v.audioSource && v.audioSource !== "None") { | 
 | ||||||
|                             if (v.audioSource === "Entire System") { |                         if (v.includeSources && v.includeSources !== "None") { | ||||||
|                                 await VesktopNative.virtmic.startSystem(v.workaround); |                             if (v.includeSources === "Entire System") { | ||||||
|  |                                 await VesktopNative.virtmic.startSystem( | ||||||
|  |                                     !v.excludeSources || isSpecialSource(v.excludeSources) ? [] : v.excludeSources | ||||||
|  |                                 ); | ||||||
|                             } else { |                             } else { | ||||||
|                                 await VesktopNative.virtmic.start([v.audioSource], v.workaround); |                                 await VesktopNative.virtmic.start(v.includeSources); | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|  | 
 | ||||||
|                         resolve(v); |                         resolve(v); | ||||||
|                     }} |                     }} | ||||||
|                     close={() => { |                     close={() => { | ||||||
|  | @ -159,6 +174,136 @@ function ScreenPicker({ screens, chooseScreen }: { screens: Source[]; chooseScre | ||||||
|     ); |     ); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function AudioSettingsModal({ | ||||||
|  |     modalProps, | ||||||
|  |     close, | ||||||
|  |     setAudioSources | ||||||
|  | }: { | ||||||
|  |     modalProps: any; | ||||||
|  |     close: () => void; | ||||||
|  |     setAudioSources: (s: AudioSources) => void; | ||||||
|  | }) { | ||||||
|  |     const Settings = useSettings(); | ||||||
|  | 
 | ||||||
|  |     return ( | ||||||
|  |         <Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}> | ||||||
|  |             <Modals.ModalHeader className="vcd-screen-picker-header"> | ||||||
|  |                 <Forms.FormTitle tag="h2">Venmic Settings</Forms.FormTitle> | ||||||
|  |                 <Modals.ModalCloseButton onClick={close} /> | ||||||
|  |             </Modals.ModalHeader> | ||||||
|  |             <Modals.ModalContent className="vcd-screen-picker-modal"> | ||||||
|  |                 <Switch | ||||||
|  |                     hideBorder | ||||||
|  |                     onChange={v => (Settings.audio = { ...Settings.audio, workaround: v })} | ||||||
|  |                     value={Settings.audio?.workaround ?? false} | ||||||
|  |                     note={ | ||||||
|  |                         <> | ||||||
|  |                             Work around an issue that causes the microphone to be shared instead of the correct audio. | ||||||
|  |                             Only enable if you're experiencing this issue. | ||||||
|  |                         </> | ||||||
|  |                     } | ||||||
|  |                 > | ||||||
|  |                     Microphone Workaround | ||||||
|  |                 </Switch> | ||||||
|  |                 <Switch | ||||||
|  |                     hideBorder | ||||||
|  |                     onChange={v => (Settings.audio = { ...Settings.audio, onlySpeakers: v })} | ||||||
|  |                     value={Settings.audio?.onlySpeakers ?? true} | ||||||
|  |                     note={ | ||||||
|  |                         <> | ||||||
|  |                             When sharing entire desktop audio, only share apps that play to a speaker. You may want to | ||||||
|  |                             disable this when using "mix bussing". | ||||||
|  |                         </> | ||||||
|  |                     } | ||||||
|  |                 > | ||||||
|  |                     Only Speakers | ||||||
|  |                 </Switch> | ||||||
|  |                 <Switch | ||||||
|  |                     hideBorder | ||||||
|  |                     onChange={v => (Settings.audio = { ...Settings.audio, onlyDefaultSpeakers: v })} | ||||||
|  |                     value={Settings.audio?.onlyDefaultSpeakers ?? true} | ||||||
|  |                     note={ | ||||||
|  |                         <> | ||||||
|  |                             When sharing entire desktop audio, only share apps that play to the <b>default</b> speakers. | ||||||
|  |                             You may want to disable this when using "mix bussing". | ||||||
|  |                         </> | ||||||
|  |                     } | ||||||
|  |                 > | ||||||
|  |                     Only Default Speakers | ||||||
|  |                 </Switch> | ||||||
|  |                 <Switch | ||||||
|  |                     hideBorder | ||||||
|  |                     onChange={v => (Settings.audio = { ...Settings.audio, ignoreInputMedia: v })} | ||||||
|  |                     value={Settings.audio?.ignoreInputMedia ?? true} | ||||||
|  |                     note={<>Exclude nodes that are intended to capture audio.</>} | ||||||
|  |                 > | ||||||
|  |                     Ignore Inputs | ||||||
|  |                 </Switch> | ||||||
|  |                 <Switch | ||||||
|  |                     hideBorder | ||||||
|  |                     onChange={v => (Settings.audio = { ...Settings.audio, ignoreVirtual: v })} | ||||||
|  |                     value={Settings.audio?.ignoreVirtual ?? false} | ||||||
|  |                     note={ | ||||||
|  |                         <> | ||||||
|  |                             Exclude virtual nodes, such as nodes belonging to loopbacks. This might be useful when using | ||||||
|  |                             "mix bussing". | ||||||
|  |                         </> | ||||||
|  |                     } | ||||||
|  |                 > | ||||||
|  |                     Ignore Virtual | ||||||
|  |                 </Switch> | ||||||
|  |                 <Switch | ||||||
|  |                     hideBorder | ||||||
|  |                     onChange={v => | ||||||
|  |                         (Settings.audio = { | ||||||
|  |                             ...Settings.audio, | ||||||
|  |                             ignoreDevices: v, | ||||||
|  |                             deviceSelect: v ? false : Settings.audio?.deviceSelect | ||||||
|  |                         }) | ||||||
|  |                     } | ||||||
|  |                     value={Settings.audio?.ignoreDevices ?? true} | ||||||
|  |                     note={<>Exclude device nodes, such as nodes belonging to microphones or speakers.</>} | ||||||
|  |                 > | ||||||
|  |                     Ignore Devices | ||||||
|  |                 </Switch> | ||||||
|  |                 <Switch | ||||||
|  |                     hideBorder | ||||||
|  |                     onChange={value => { | ||||||
|  |                         Settings.audio = { ...Settings.audio, granularSelect: value }; | ||||||
|  |                         setAudioSources("None"); | ||||||
|  |                     }} | ||||||
|  |                     value={Settings.audio?.granularSelect ?? false} | ||||||
|  |                     note={<>Allow to select applications more granularly.</>} | ||||||
|  |                 > | ||||||
|  |                     Granular Selection | ||||||
|  |                 </Switch> | ||||||
|  |                 <Switch | ||||||
|  |                     hideBorder | ||||||
|  |                     onChange={value => { | ||||||
|  |                         Settings.audio = { ...Settings.audio, deviceSelect: value }; | ||||||
|  |                         setAudioSources("None"); | ||||||
|  |                     }} | ||||||
|  |                     value={Settings.audio?.deviceSelect ?? false} | ||||||
|  |                     disabled={Settings.audio?.ignoreDevices} | ||||||
|  |                     note={ | ||||||
|  |                         <> | ||||||
|  |                             Allow to select devices such as microphones. Requires <b>Ignore Devices</b> to be turned | ||||||
|  |                             off. | ||||||
|  |                         </> | ||||||
|  |                     } | ||||||
|  |                 > | ||||||
|  |                     Device Selection | ||||||
|  |                 </Switch> | ||||||
|  |             </Modals.ModalContent> | ||||||
|  |             <Modals.ModalFooter className="vcd-screen-picker-footer"> | ||||||
|  |                 <Button color={Button.Colors.TRANSPARENT} onClick={close}> | ||||||
|  |                     Back | ||||||
|  |                 </Button> | ||||||
|  |             </Modals.ModalFooter> | ||||||
|  |         </Modals.ModalRoot> | ||||||
|  |     ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| function StreamSettings({ | function StreamSettings({ | ||||||
|     source, |     source, | ||||||
|     settings, |     settings, | ||||||
|  | @ -170,6 +315,8 @@ function StreamSettings({ | ||||||
|     setSettings: Dispatch<SetStateAction<StreamSettings>>; |     setSettings: Dispatch<SetStateAction<StreamSettings>>; | ||||||
|     skipPicker: boolean; |     skipPicker: boolean; | ||||||
| }) { | }) { | ||||||
|  |     const Settings = useSettings(); | ||||||
|  | 
 | ||||||
|     const [thumb] = useAwaiter( |     const [thumb] = useAwaiter( | ||||||
|         () => (skipPicker ? Promise.resolve(source.url) : VesktopNative.capturer.getLargeThumbnail(source.id)), |         () => (skipPicker ? Promise.resolve(source.url) : VesktopNative.capturer.getLargeThumbnail(source.id)), | ||||||
|         { |         { | ||||||
|  | @ -178,230 +325,363 @@ function StreamSettings({ | ||||||
|         } |         } | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|  |     const openSettings = () => { | ||||||
|  |         const key = openModal(props => ( | ||||||
|  |             <AudioSettingsModal | ||||||
|  |                 modalProps={props} | ||||||
|  |                 close={() => props.onClose()} | ||||||
|  |                 setAudioSources={sources => | ||||||
|  |                     setSettings(s => ({ ...s, includeSources: sources, excludeSources: sources })) | ||||||
|  |                 } | ||||||
|  |             /> | ||||||
|  |         )); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|     return ( |     return ( | ||||||
|         <div className={isLinux ? "vcd-screen-picker-settings-grid" : ""}> |         <div> | ||||||
|             <div> |             <Forms.FormTitle>What you're streaming</Forms.FormTitle> | ||||||
|                 <Forms.FormTitle>What you're streaming</Forms.FormTitle> |             <Card className="vcd-screen-picker-card vcd-screen-picker-preview"> | ||||||
|                 <Card className="vcd-screen-picker-card vcd-screen-picker-preview"> |                 <img | ||||||
|                     <img |                     src={thumb} | ||||||
|                         src={thumb} |                     alt="" | ||||||
|                         alt="" |                     className={isLinux ? "vcd-screen-picker-preview-img-linux" : "vcd-screen-picker-preview-img"} | ||||||
|                         className={isLinux ? "vcd-screen-picker-preview-img-linux" : "vcd-screen-picker-preview-img"} |                 /> | ||||||
|                     /> |                 <Text variant="text-sm/normal">{source.name}</Text> | ||||||
|                     <Text variant="text-sm/normal">{source.name}</Text> |             </Card> | ||||||
|                 </Card> |  | ||||||
| 
 | 
 | ||||||
|                 <Forms.FormTitle>Stream Settings</Forms.FormTitle> |             <Forms.FormTitle>Stream Settings</Forms.FormTitle> | ||||||
| 
 | 
 | ||||||
|                 <Card className="vcd-screen-picker-card"> |             <Card className="vcd-screen-picker-card"> | ||||||
|                     <div className="vcd-screen-picker-quality"> |                 <div className="vcd-screen-picker-quality"> | ||||||
|                         <section> |                     <section> | ||||||
|                             <Forms.FormTitle>Resolution</Forms.FormTitle> |                         <Forms.FormTitle>Resolution</Forms.FormTitle> | ||||||
|  |                         <div className="vcd-screen-picker-radios"> | ||||||
|  |                             {StreamResolutions.map(res => ( | ||||||
|  |                                 <label className="vcd-screen-picker-radio" data-checked={settings.resolution === res}> | ||||||
|  |                                     <Text variant="text-sm/bold">{res}</Text> | ||||||
|  |                                     <input | ||||||
|  |                                         type="radio" | ||||||
|  |                                         name="resolution" | ||||||
|  |                                         value={res} | ||||||
|  |                                         checked={settings.resolution === res} | ||||||
|  |                                         onChange={() => setSettings(s => ({ ...s, resolution: res }))} | ||||||
|  |                                     /> | ||||||
|  |                                 </label> | ||||||
|  |                             ))} | ||||||
|  |                         </div> | ||||||
|  |                     </section> | ||||||
|  | 
 | ||||||
|  |                     <section> | ||||||
|  |                         <Forms.FormTitle>Frame Rate</Forms.FormTitle> | ||||||
|  |                         <div className="vcd-screen-picker-radios"> | ||||||
|  |                             {StreamFps.map(fps => ( | ||||||
|  |                                 <label className="vcd-screen-picker-radio" data-checked={settings.fps === fps}> | ||||||
|  |                                     <Text variant="text-sm/bold">{fps}</Text> | ||||||
|  |                                     <input | ||||||
|  |                                         type="radio" | ||||||
|  |                                         name="fps" | ||||||
|  |                                         value={fps} | ||||||
|  |                                         checked={settings.fps === fps} | ||||||
|  |                                         onChange={() => setSettings(s => ({ ...s, fps }))} | ||||||
|  |                                     /> | ||||||
|  |                                 </label> | ||||||
|  |                             ))} | ||||||
|  |                         </div> | ||||||
|  |                     </section> | ||||||
|  |                 </div> | ||||||
|  |                 <div className="vcd-screen-picker-quality"> | ||||||
|  |                     <section> | ||||||
|  |                         <Forms.FormTitle>Content Type</Forms.FormTitle> | ||||||
|  |                         <div> | ||||||
|                             <div className="vcd-screen-picker-radios"> |                             <div className="vcd-screen-picker-radios"> | ||||||
|                                 {StreamResolutions.map(res => ( |                                 <label | ||||||
|                                     <label |                                     className="vcd-screen-picker-radio" | ||||||
|                                         className="vcd-screen-picker-radio" |                                     data-checked={settings.contentHint === "motion"} | ||||||
|                                         data-checked={settings.resolution === res} |  | ||||||
|                                     > |  | ||||||
|                                         <Text variant="text-sm/bold">{res}</Text> |  | ||||||
|                                         <input |  | ||||||
|                                             type="radio" |  | ||||||
|                                             name="resolution" |  | ||||||
|                                             value={res} |  | ||||||
|                                             checked={settings.resolution === res} |  | ||||||
|                                             onChange={() => setSettings(s => ({ ...s, resolution: res }))} |  | ||||||
|                                         /> |  | ||||||
|                                     </label> |  | ||||||
|                                 ))} |  | ||||||
|                             </div> |  | ||||||
|                         </section> |  | ||||||
| 
 |  | ||||||
|                         <section> |  | ||||||
|                             <Forms.FormTitle>Frame Rate</Forms.FormTitle> |  | ||||||
|                             <div className="vcd-screen-picker-radios"> |  | ||||||
|                                 {StreamFps.map(fps => ( |  | ||||||
|                                     <label className="vcd-screen-picker-radio" data-checked={settings.fps === fps}> |  | ||||||
|                                         <Text variant="text-sm/bold">{fps}</Text> |  | ||||||
|                                         <input |  | ||||||
|                                             type="radio" |  | ||||||
|                                             name="fps" |  | ||||||
|                                             value={fps} |  | ||||||
|                                             checked={settings.fps === fps} |  | ||||||
|                                             onChange={() => setSettings(s => ({ ...s, fps }))} |  | ||||||
|                                         /> |  | ||||||
|                                     </label> |  | ||||||
|                                 ))} |  | ||||||
|                             </div> |  | ||||||
|                         </section> |  | ||||||
|                     </div> |  | ||||||
|                     <div className="vcd-screen-picker-quality"> |  | ||||||
|                         <section> |  | ||||||
|                             <Forms.FormTitle>Content Type</Forms.FormTitle> |  | ||||||
|                             <div> |  | ||||||
|                                 <div className="vcd-screen-picker-radios"> |  | ||||||
|                                     <label |  | ||||||
|                                         className="vcd-screen-picker-radio" |  | ||||||
|                                         data-checked={settings.contentHint === "motion"} |  | ||||||
|                                     > |  | ||||||
|                                         <Text variant="text-sm/bold">Prefer Smoothness</Text> |  | ||||||
|                                         <input |  | ||||||
|                                             type="radio" |  | ||||||
|                                             name="contenthint" |  | ||||||
|                                             value="motion" |  | ||||||
|                                             checked={settings.contentHint === "motion"} |  | ||||||
|                                             onChange={() => setSettings(s => ({ ...s, contentHint: "motion" }))} |  | ||||||
|                                         /> |  | ||||||
|                                     </label> |  | ||||||
|                                     <label |  | ||||||
|                                         className="vcd-screen-picker-radio" |  | ||||||
|                                         data-checked={settings.contentHint === "detail"} |  | ||||||
|                                     > |  | ||||||
|                                         <Text variant="text-sm/bold">Prefer Clarity</Text> |  | ||||||
|                                         <input |  | ||||||
|                                             type="radio" |  | ||||||
|                                             name="contenthint" |  | ||||||
|                                             value="detail" |  | ||||||
|                                             checked={settings.contentHint === "detail"} |  | ||||||
|                                             onChange={() => setSettings(s => ({ ...s, contentHint: "detail" }))} |  | ||||||
|                                         /> |  | ||||||
|                                     </label> |  | ||||||
|                                 </div> |  | ||||||
|                                 <div className="vcd-screen-picker-hint-description"> |  | ||||||
|                                     <p> |  | ||||||
|                                         Choosing "Prefer Clarity" will result in a significantly lower framerate in |  | ||||||
|                                         exchange for a much sharper and clearer image. |  | ||||||
|                                     </p> |  | ||||||
|                                 </div> |  | ||||||
|                             </div> |  | ||||||
|                             {isWindows && ( |  | ||||||
|                                 <Switch |  | ||||||
|                                     value={settings.audio} |  | ||||||
|                                     onChange={checked => setSettings(s => ({ ...s, audio: checked }))} |  | ||||||
|                                     hideBorder |  | ||||||
|                                     className="vcd-screen-picker-audio" |  | ||||||
|                                 > |                                 > | ||||||
|                                     Stream With Audio |                                     <Text variant="text-sm/bold">Prefer Smoothness</Text> | ||||||
|                                 </Switch> |                                     <input | ||||||
|                             )} |                                         type="radio" | ||||||
|                         </section> |                                         name="contenthint" | ||||||
|                     </div> |                                         value="motion" | ||||||
|                 </Card> |                                         checked={settings.contentHint === "motion"} | ||||||
|             </div> |                                         onChange={() => setSettings(s => ({ ...s, contentHint: "motion" }))} | ||||||
|  |                                     /> | ||||||
|  |                                 </label> | ||||||
|  |                                 <label | ||||||
|  |                                     className="vcd-screen-picker-radio" | ||||||
|  |                                     data-checked={settings.contentHint === "detail"} | ||||||
|  |                                 > | ||||||
|  |                                     <Text variant="text-sm/bold">Prefer Clarity</Text> | ||||||
|  |                                     <input | ||||||
|  |                                         type="radio" | ||||||
|  |                                         name="contenthint" | ||||||
|  |                                         value="detail" | ||||||
|  |                                         checked={settings.contentHint === "detail"} | ||||||
|  |                                         onChange={() => setSettings(s => ({ ...s, contentHint: "detail" }))} | ||||||
|  |                                     /> | ||||||
|  |                                 </label> | ||||||
|  |                             </div> | ||||||
|  |                             <div className="vcd-screen-picker-hint-description"> | ||||||
|  |                                 <p> | ||||||
|  |                                     Choosing "Prefer Clarity" will result in a significantly lower framerate in exchange | ||||||
|  |                                     for a much sharper and clearer image. | ||||||
|  |                                 </p> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                         {isWindows && ( | ||||||
|  |                             <Switch | ||||||
|  |                                 value={settings.audio} | ||||||
|  |                                 onChange={checked => setSettings(s => ({ ...s, audio: checked }))} | ||||||
|  |                                 hideBorder | ||||||
|  |                                 className="vcd-screen-picker-audio" | ||||||
|  |                             > | ||||||
|  |                                 Stream With Audio | ||||||
|  |                             </Switch> | ||||||
|  |                         )} | ||||||
|  |                     </section> | ||||||
|  |                 </div> | ||||||
| 
 | 
 | ||||||
|             <div> |  | ||||||
|                 {isLinux && ( |                 {isLinux && ( | ||||||
|                     <AudioSourcePickerLinux |                     <AudioSourcePickerLinux | ||||||
|                         audioSource={settings.audioSource} |                         openSettings={openSettings} | ||||||
|                         workaround={settings.workaround} |                         includeSources={settings.includeSources} | ||||||
|                         onlyDefaultSpeakers={settings.onlyDefaultSpeakers} |                         excludeSources={settings.excludeSources} | ||||||
|                         setAudioSource={source => setSettings(s => ({ ...s, audioSource: source }))} |                         deviceSelect={Settings.audio?.deviceSelect} | ||||||
|                         setWorkaround={value => setSettings(s => ({ ...s, workaround: value }))} |                         granularSelect={Settings.audio?.granularSelect} | ||||||
|                         setOnlyDefaultSpeakers={value => setSettings(s => ({ ...s, onlyDefaultSpeakers: value }))} |                         setIncludeSources={sources => setSettings(s => ({ ...s, includeSources: sources }))} | ||||||
|  |                         setExcludeSources={sources => setSettings(s => ({ ...s, excludeSources: sources }))} | ||||||
|                     /> |                     /> | ||||||
|                 )} |                 )} | ||||||
|             </div> |             </Card> | ||||||
|         </div> |         </div> | ||||||
|     ); |     ); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function isSpecialSource(value?: AudioSource | AudioSources): value is SpecialSource { | ||||||
|  |     return typeof value === "string"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function hasMatchingProps(value: Node, other: Node) { | ||||||
|  |     return Object.keys(value).every(key => value[key] === other[key]); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function mapToAudioItem(node: AudioSource, granularSelect?: boolean, deviceSelect?: boolean): AudioItem[] { | ||||||
|  |     if (isSpecialSource(node)) { | ||||||
|  |         return [{ name: node, value: node }]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const rtn: AudioItem[] = []; | ||||||
|  | 
 | ||||||
|  |     const mediaClass = node["media.class"]; | ||||||
|  | 
 | ||||||
|  |     if (mediaClass?.includes("Video") || mediaClass?.includes("Midi")) { | ||||||
|  |         return rtn; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (!deviceSelect && node["device.id"]) { | ||||||
|  |         return rtn; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const name = node["application.name"]; | ||||||
|  | 
 | ||||||
|  |     if (name) { | ||||||
|  |         rtn.push({ name: name, value: { "application.name": name } }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (!granularSelect) { | ||||||
|  |         return rtn; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const rawName = node["node.name"]; | ||||||
|  | 
 | ||||||
|  |     if (!name) { | ||||||
|  |         rtn.push({ name: rawName, value: { "node.name": rawName } }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const binary = node["application.process.binary"]; | ||||||
|  | 
 | ||||||
|  |     if (!name && binary) { | ||||||
|  |         rtn.push({ name: binary, value: { "application.process.binary": binary } }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const pid = node["application.process.id"]; | ||||||
|  | 
 | ||||||
|  |     const first = rtn[0]; | ||||||
|  |     const firstValues = first.value as Node; | ||||||
|  | 
 | ||||||
|  |     if (pid) { | ||||||
|  |         rtn.push({ | ||||||
|  |             name: `${first.name} (${pid})`, | ||||||
|  |             value: { ...firstValues, "application.process.id": pid } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const mediaName = node["media.name"]; | ||||||
|  | 
 | ||||||
|  |     if (mediaName) { | ||||||
|  |         rtn.push({ | ||||||
|  |             name: `${first.name} [${mediaName}]`, | ||||||
|  |             value: { ...firstValues, "media.name": mediaName } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (mediaClass) { | ||||||
|  |         rtn.push({ | ||||||
|  |             name: `${first.name} [${mediaClass}]`, | ||||||
|  |             value: { ...firstValues, "media.class": mediaClass } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return rtn; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function isItemSelected(sources?: AudioSources) { | ||||||
|  |     return (value: AudioSource) => { | ||||||
|  |         if (!sources) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (isSpecialSource(sources) || isSpecialSource(value)) { | ||||||
|  |             return sources === value; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return sources.some(source => hasMatchingProps(source, value)); | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function updateItems(setSources: (s: AudioSources) => void, sources?: AudioSources) { | ||||||
|  |     return (value: AudioSource) => { | ||||||
|  |         if (isSpecialSource(value)) { | ||||||
|  |             setSources(value); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (isSpecialSource(sources)) { | ||||||
|  |             setSources([value]); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (isItemSelected(sources)(value)) { | ||||||
|  |             setSources(sources?.filter(x => !hasMatchingProps(x, value)) ?? "None"); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         setSources([...(sources || []), value]); | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| function AudioSourcePickerLinux({ | function AudioSourcePickerLinux({ | ||||||
|     audioSource, |     includeSources, | ||||||
|     workaround, |     excludeSources, | ||||||
|     onlyDefaultSpeakers, |     deviceSelect, | ||||||
|     setAudioSource, |     granularSelect, | ||||||
|     setWorkaround, |     openSettings, | ||||||
|     setOnlyDefaultSpeakers |     setIncludeSources, | ||||||
|  |     setExcludeSources | ||||||
| }: { | }: { | ||||||
|     audioSource?: string; |     includeSources?: AudioSources; | ||||||
|     workaround?: boolean; |     excludeSources?: AudioSources; | ||||||
|     onlyDefaultSpeakers?: boolean; |     deviceSelect?: boolean; | ||||||
|     setAudioSource(s: string): void; |     granularSelect?: boolean; | ||||||
|     setWorkaround(b: boolean): void; |     openSettings: () => void; | ||||||
|     setOnlyDefaultSpeakers(b: boolean): void; |     setIncludeSources: (s: AudioSources) => void; | ||||||
|  |     setExcludeSources: (s: AudioSources) => void; | ||||||
| }) { | }) { | ||||||
|     const [sources, _, loading] = useAwaiter(() => VesktopNative.virtmic.list(), { |     const [sources, _, loading] = useAwaiter(() => VesktopNative.virtmic.list(), { | ||||||
|         fallbackValue: { ok: true, targets: [], hasPipewirePulse: true } |         fallbackValue: { ok: true, targets: [], hasPipewirePulse: true } | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     const allSources = sources.ok ? ["None", "Entire System", ...sources.targets] : null; |  | ||||||
|     const hasPipewirePulse = sources.ok ? sources.hasPipewirePulse : true; |     const hasPipewirePulse = sources.ok ? sources.hasPipewirePulse : true; | ||||||
| 
 |  | ||||||
|     const [ignorePulseWarning, setIgnorePulseWarning] = useState(false); |     const [ignorePulseWarning, setIgnorePulseWarning] = useState(false); | ||||||
| 
 | 
 | ||||||
|  |     if (!sources.ok && sources.isGlibCxxOutdated) { | ||||||
|  |         return ( | ||||||
|  |             <Forms.FormText> | ||||||
|  |                 Failed to retrieve Audio Sources because your C++ library is too old to run | ||||||
|  |                 <a href="https://github.com/Vencord/venmic" target="_blank"> | ||||||
|  |                     venmic | ||||||
|  |                 </a> | ||||||
|  |                 . See{" "} | ||||||
|  |                 <a href="https://gist.github.com/Vendicated/b655044ffbb16b2716095a448c6d827a" target="_blank"> | ||||||
|  |                     this guide | ||||||
|  |                 </a>{" "} | ||||||
|  |                 for possible solutions. | ||||||
|  |             </Forms.FormText> | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (!hasPipewirePulse && !ignorePulseWarning) { | ||||||
|  |         return ( | ||||||
|  |             <Text variant="text-sm/normal"> | ||||||
|  |                 Could not find pipewire-pulse. See{" "} | ||||||
|  |                 <a href="https://gist.github.com/the-spyke/2de98b22ff4f978ebf0650c90e82027e#install" target="_blank"> | ||||||
|  |                     this guide | ||||||
|  |                 </a>{" "} | ||||||
|  |                 on how to switch to pipewire. <br /> | ||||||
|  |                 You can still continue, however, please{" "} | ||||||
|  |                 <b>beware that you can only share audio of apps that are running under pipewire</b>.{" "} | ||||||
|  |                 <a onClick={() => setIgnorePulseWarning(true)}>I know what I'm doing!</a> | ||||||
|  |             </Text> | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const specialSources: SpecialSource[] = ["None", "Entire System"] as const; | ||||||
|  | 
 | ||||||
|  |     const uniqueName = (value: AudioItem, index: number, list: AudioItem[]) => | ||||||
|  |         list.findIndex(x => x.name === value.name) === index; | ||||||
|  | 
 | ||||||
|  |     const allSources = sources.ok | ||||||
|  |         ? [...specialSources, ...sources.targets] | ||||||
|  |               .map(target => mapToAudioItem(target, granularSelect, deviceSelect)) | ||||||
|  |               .flat() | ||||||
|  |               .filter(uniqueName) | ||||||
|  |         : []; | ||||||
|  | 
 | ||||||
|     return ( |     return ( | ||||||
|         <> |         <> | ||||||
|             <Forms.FormTitle>Audio Settings</Forms.FormTitle> |             <div className={includeSources === "Entire System" ? "vcd-screen-picker-quality" : undefined}> | ||||||
|             <Card className="vcd-screen-picker-card"> |                 <section> | ||||||
|                 {loading ? ( |                     <Forms.FormTitle>{loading ? "Loading Sources..." : "Audio Sources"}</Forms.FormTitle> | ||||||
|                     <Forms.FormTitle>Loading Audio Sources...</Forms.FormTitle> |                     <Select | ||||||
|                 ) : ( |                         options={allSources.map(({ name, value }) => ({ | ||||||
|                     <Forms.FormTitle>Audio Source</Forms.FormTitle> |                             label: name, | ||||||
|                 )} |                             value: value, | ||||||
| 
 |                             default: name === "None" | ||||||
|                 {!sources.ok && sources.isGlibCxxOutdated && ( |                         }))} | ||||||
|                     <Forms.FormText> |                         isSelected={isItemSelected(includeSources)} | ||||||
|                         Failed to retrieve Audio Sources because your C++ library is too old to run |                         select={updateItems(setIncludeSources, includeSources)} | ||||||
|                         <a href="https://github.com/Vencord/venmic" target="_blank"> |                         serialize={String} | ||||||
|                             venmic |                         popoutPosition="top" | ||||||
|                         </a> |                         closeOnSelect={false} | ||||||
|                         . See{" "} |                     /> | ||||||
|                         <a href="https://gist.github.com/Vendicated/b655044ffbb16b2716095a448c6d827a" target="_blank"> |                 </section> | ||||||
|                             this guide |                 {includeSources === "Entire System" && ( | ||||||
|                         </a>{" "} |                     <section> | ||||||
|                         for possible solutions. |                         <Forms.FormTitle>Exclude Sources</Forms.FormTitle> | ||||||
|                     </Forms.FormText> |  | ||||||
|                 )} |  | ||||||
| 
 |  | ||||||
|                 {hasPipewirePulse || ignorePulseWarning ? ( |  | ||||||
|                     allSources && ( |  | ||||||
|                         <Select |                         <Select | ||||||
|                             options={allSources.map(s => ({ label: s, value: s, default: s === "None" }))} |                             options={allSources | ||||||
|                             isSelected={s => s === audioSource} |                                 .filter(x => x.name !== "Entire System") | ||||||
|                             select={setAudioSource} |                                 .map(({ name, value }) => ({ | ||||||
|  |                                     label: name, | ||||||
|  |                                     value: value, | ||||||
|  |                                     default: name === "None" | ||||||
|  |                                 }))} | ||||||
|  |                             isSelected={isItemSelected(excludeSources)} | ||||||
|  |                             select={updateItems(setExcludeSources, excludeSources)} | ||||||
|                             serialize={String} |                             serialize={String} | ||||||
|  |                             popoutPosition="top" | ||||||
|  |                             closeOnSelect={false} | ||||||
|                         /> |                         /> | ||||||
|                     ) |                     </section> | ||||||
|                 ) : ( |  | ||||||
|                     <Text variant="text-sm/normal"> |  | ||||||
|                         Could not find pipewire-pulse. This usually means that you do not run pipewire as your main |  | ||||||
|                         audio-server. <br /> |  | ||||||
|                         You can still continue, however, please beware that you can only share audio of apps that are |  | ||||||
|                         running under pipewire. |  | ||||||
|                         <br /> |  | ||||||
|                         <a onClick={() => setIgnorePulseWarning(true)}>I know what I'm doing</a> |  | ||||||
|                     </Text> |  | ||||||
|                 )} |                 )} | ||||||
| 
 |             </div> | ||||||
|                 <Forms.FormDivider className={Margins.top16 + " " + Margins.bottom16} /> |             <Button | ||||||
| 
 |                 color={Button.Colors.TRANSPARENT} | ||||||
|                 <Switch |                 onClick={openSettings} | ||||||
|                     onChange={setWorkaround} |                 className="vcd-screen-picker-settings-button" | ||||||
|                     value={workaround ?? false} |             > | ||||||
|                     note={ |                 Open Audio Settings | ||||||
|                         <> |             </Button> | ||||||
|                             Work around an issue that causes the microphone to be shared instead of the correct audio. |  | ||||||
|                             Only enable if you're experiencing this issue. |  | ||||||
|                         </> |  | ||||||
|                     } |  | ||||||
|                 > |  | ||||||
|                     Microphone Workaround |  | ||||||
|                 </Switch> |  | ||||||
| 
 |  | ||||||
|                 <Switch |  | ||||||
|                     hideBorder |  | ||||||
|                     onChange={setOnlyDefaultSpeakers} |  | ||||||
|                     disabled={audioSource !== "Entire System"} |  | ||||||
|                     value={onlyDefaultSpeakers ?? true} |  | ||||||
|                     note={ |  | ||||||
|                         <> |  | ||||||
|                             When sharing entire desktop audio, only share apps that play to the default speakers and |  | ||||||
|                             ignore apps that play to other speakers or devices. |  | ||||||
|                         </> |  | ||||||
|                     } |  | ||||||
|                 > |  | ||||||
|                     Only Default Speakers |  | ||||||
|                 </Switch> |  | ||||||
|             </Card> |  | ||||||
|         </> |         </> | ||||||
|     ); |     ); | ||||||
| } | } | ||||||
|  | @ -421,10 +701,11 @@ function ModalComponent({ | ||||||
| }) { | }) { | ||||||
|     const [selected, setSelected] = useState<string | undefined>(skipPicker ? screens[0].id : void 0); |     const [selected, setSelected] = useState<string | undefined>(skipPicker ? screens[0].id : void 0); | ||||||
|     const [settings, setSettings] = useState<StreamSettings>({ |     const [settings, setSettings] = useState<StreamSettings>({ | ||||||
|         resolution: "1080", |         resolution: "720", | ||||||
|         fps: "60", |         fps: "30", | ||||||
|         contentHint: "motion", |         contentHint: "motion", | ||||||
|         audio: true |         audio: true, | ||||||
|  |         includeSources: "None" | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|  | @ -480,7 +761,7 @@ function ModalComponent({ | ||||||
| 
 | 
 | ||||||
|                                 const constraints = { |                                 const constraints = { | ||||||
|                                     ...track.getConstraints(), |                                     ...track.getConstraints(), | ||||||
|                                     frameRate, |                                     frameRate: { min: frameRate, ideal: frameRate }, | ||||||
|                                     width: { min: 640, ideal: width, max: width }, |                                     width: { min: 640, ideal: width, max: width }, | ||||||
|                                     height: { min: 480, ideal: height, max: height }, |                                     height: { min: 480, ideal: height, max: height }, | ||||||
|                                     advanced: [{ width: width, height: height }], |                                     advanced: [{ width: width, height: height }], | ||||||
|  |  | ||||||
|  | @ -11,17 +11,6 @@ | ||||||
|     gap: 1em; |     gap: 1em; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .vcd-screen-picker-settings-grid { |  | ||||||
|     gap: 1em; |  | ||||||
|     display: grid; |  | ||||||
|     grid-template-columns: 1fr 1fr; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .vcd-screen-picker-settings-grid>div { |  | ||||||
|     display: flex; |  | ||||||
|     flex-direction: column; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .vcd-screen-picker-card { | .vcd-screen-picker-card { | ||||||
|     flex-grow: 1; |     flex-grow: 1; | ||||||
| } | } | ||||||
|  | @ -38,7 +27,7 @@ | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .vcd-screen-picker-selected img { | .vcd-screen-picker-selected img { | ||||||
|     border: 2px solid var(--brand-experiment); |     border: 2px solid var(--brand-500); | ||||||
|     border-radius: 3px; |     border-radius: 3px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -49,7 +38,7 @@ | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .vcd-screen-picker-grid label:hover { | .vcd-screen-picker-grid label:hover { | ||||||
|     outline: 2px solid var(--brand-experiment); |     outline: 2px solid var(--brand-500); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -68,7 +57,7 @@ | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .vcd-screen-picker-preview-img-linux { | .vcd-screen-picker-preview-img-linux { | ||||||
|     width: 100%; |     width: 60%; | ||||||
|     margin-bottom: 0.5em; |     margin-bottom: 0.5em; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -101,8 +90,8 @@ | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .vcd-screen-picker-radio[data-checked="true"] { | .vcd-screen-picker-radio[data-checked="true"] { | ||||||
|     background-color: var(--brand-experiment); |     background-color: var(--brand-500); | ||||||
|     border-color: var(--brand-experiment); |     border-color: var(--brand-500); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .vcd-screen-picker-radio[data-checked="true"] h2 { | .vcd-screen-picker-radio[data-checked="true"] h2 { | ||||||
|  | @ -120,6 +109,11 @@ | ||||||
|     flex: 1 1 auto; |     flex: 1 1 auto; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .vcd-screen-picker-settings-button { | ||||||
|  |     margin-left: auto; | ||||||
|  |     margin-top: 0.3rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .vcd-screen-picker-radios { | .vcd-screen-picker-radios { | ||||||
|     display: flex; |     display: flex; | ||||||
|     width: 100%; |     width: 100%; | ||||||
|  |  | ||||||
|  | @ -103,16 +103,7 @@ const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>> | ||||||
|             defaultValue: false |             defaultValue: false | ||||||
|         } |         } | ||||||
|     ], |     ], | ||||||
|     "Notifications & Updates": [ |     Notifications: [NotificationBadgeToggle, TrayNotificationBadgeToggle], | ||||||
|         NotificationBadgeToggle, |  | ||||||
|         TrayNotificationBadgeToggle, |  | ||||||
|         { |  | ||||||
|             key: "checkUpdates", |  | ||||||
|             title: "Check for updates", |  | ||||||
|             description: "Automatically check for Vesktop updates", |  | ||||||
|             defaultValue: true |  | ||||||
|         } |  | ||||||
|     ], |  | ||||||
|     Miscelleanous: [ |     Miscelleanous: [ | ||||||
|         { |         { | ||||||
|             key: "arRPC", |             key: "arRPC", | ||||||
|  |  | ||||||
|  | @ -4,24 +4,28 @@ | ||||||
|  * Copyright (c) 2023 Vendicated and Vencord contributors |  * Copyright (c) 2023 Vendicated and Vencord contributors | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
|  | import { useForceUpdater } from "@vencord/types/utils"; | ||||||
| import { Button, Forms, Toasts } from "@vencord/types/webpack/common"; | import { Button, Forms, Toasts } from "@vencord/types/webpack/common"; | ||||||
| 
 | 
 | ||||||
| import { SettingsComponent } from "./Settings"; | import { SettingsComponent } from "./Settings"; | ||||||
| 
 | 
 | ||||||
| export const VencordLocationPicker: SettingsComponent = ({ settings }) => { | export const VencordLocationPicker: SettingsComponent = ({ settings }) => { | ||||||
|  |     const forceUpdate = useForceUpdater(); | ||||||
|  |     const vencordDir = VesktopNative.fileManager.getVencordDir(); | ||||||
|  | 
 | ||||||
|     return ( |     return ( | ||||||
|         <> |         <> | ||||||
|             <Forms.FormText> |             <Forms.FormText> | ||||||
|                 Vencord files are loaded from{" "} |                 Vencord files are loaded from{" "} | ||||||
|                 {settings.vencordDir ? ( |                 {vencordDir ? ( | ||||||
|                     <a |                     <a | ||||||
|                         href="about:blank" |                         href="about:blank" | ||||||
|                         onClick={e => { |                         onClick={e => { | ||||||
|                             e.preventDefault(); |                             e.preventDefault(); | ||||||
|                             VesktopNative.fileManager.showItemInFolder(settings.vencordDir!); |                             VesktopNative.fileManager.showItemInFolder(vencordDir!); | ||||||
|                         }} |                         }} | ||||||
|                     > |                     > | ||||||
|                         {settings.vencordDir} |                         {vencordDir} | ||||||
|                     </a> |                     </a> | ||||||
|                 ) : ( |                 ) : ( | ||||||
|                     "the default location" |                     "the default location" | ||||||
|  | @ -34,7 +38,14 @@ export const VencordLocationPicker: SettingsComponent = ({ settings }) => { | ||||||
|                         const choice = await VesktopNative.fileManager.selectVencordDir(); |                         const choice = await VesktopNative.fileManager.selectVencordDir(); | ||||||
|                         switch (choice) { |                         switch (choice) { | ||||||
|                             case "cancelled": |                             case "cancelled": | ||||||
|                                 return; |                                 break; | ||||||
|  |                             case "ok": | ||||||
|  |                                 Toasts.show({ | ||||||
|  |                                     message: "Vencord install changed. Fully restart Vesktop to apply.", | ||||||
|  |                                     id: Toasts.genId(), | ||||||
|  |                                     type: Toasts.Type.SUCCESS | ||||||
|  |                                 }); | ||||||
|  |                                 break; | ||||||
|                             case "invalid": |                             case "invalid": | ||||||
|                                 Toasts.show({ |                                 Toasts.show({ | ||||||
|                                     message: |                                     message: | ||||||
|  | @ -42,9 +53,9 @@ export const VencordLocationPicker: SettingsComponent = ({ settings }) => { | ||||||
|                                     id: Toasts.genId(), |                                     id: Toasts.genId(), | ||||||
|                                     type: Toasts.Type.FAILURE |                                     type: Toasts.Type.FAILURE | ||||||
|                                 }); |                                 }); | ||||||
|                                 return; |                                 break; | ||||||
|                         } |                         } | ||||||
|                         settings.vencordDir = choice; |                         forceUpdate(); | ||||||
|                     }} |                     }} | ||||||
|                 > |                 > | ||||||
|                     Change |                     Change | ||||||
|  | @ -52,7 +63,10 @@ export const VencordLocationPicker: SettingsComponent = ({ settings }) => { | ||||||
|                 <Button |                 <Button | ||||||
|                     size={Button.Sizes.SMALL} |                     size={Button.Sizes.SMALL} | ||||||
|                     color={Button.Colors.RED} |                     color={Button.Colors.RED} | ||||||
|                     onClick={() => (settings.vencordDir = void 0)} |                     onClick={async () => { | ||||||
|  |                         await VesktopNative.fileManager.selectVencordDir(null); | ||||||
|  |                         forceUpdate(); | ||||||
|  |                     }} | ||||||
|                 > |                 > | ||||||
|                     Reset |                     Reset | ||||||
|                 </Button> |                 </Button> | ||||||
|  |  | ||||||
|  | @ -9,3 +9,8 @@ | ||||||
|     scrollbar-width: unset !important; |     scrollbar-width: unset !important; | ||||||
|     scrollbar-color: unset !important; |     scrollbar-color: unset !important; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /* Workaround for making things in the draggable area clickable again on macOS */ | ||||||
|  | .platform-osx [class*=topic_], .platform-osx [class*=notice_] button { | ||||||
|  |     -webkit-app-region: no-drag; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -12,8 +12,8 @@ import "./themedSplash"; | ||||||
| console.log("read if cute :3"); | console.log("read if cute :3"); | ||||||
| 
 | 
 | ||||||
| export * as Components from "./components"; | export * as Components from "./components"; | ||||||
| import { findByPropsLazy } from "@vencord/types/webpack"; | import { findByPropsLazy, onceReady } from "@vencord/types/webpack"; | ||||||
| import { FluxDispatcher } from "@vencord/types/webpack/common"; | import { Alerts, FluxDispatcher } from "@vencord/types/webpack/common"; | ||||||
| 
 | 
 | ||||||
| import SettingsUi from "./components/settings/Settings"; | import SettingsUi from "./components/settings/Settings"; | ||||||
| import { Settings } from "./settings"; | import { Settings } from "./settings"; | ||||||
|  | @ -52,8 +52,26 @@ const arRPC = Vencord.Plugins.plugins["WebRichPresence (arRPC)"] as any as { | ||||||
|     handleEvent(e: MessageEvent): void; |     handleEvent(e: MessageEvent): void; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| VesktopNative.arrpc.onActivity(data => { | VesktopNative.arrpc.onActivity(async data => { | ||||||
|     if (!Settings.store.arRPC) return; |     if (!Settings.store.arRPC) return; | ||||||
| 
 | 
 | ||||||
|  |     await onceReady; | ||||||
|  | 
 | ||||||
|     arRPC.handleEvent(new MessageEvent("message", { data })); |     arRPC.handleEvent(new MessageEvent("message", { data })); | ||||||
| }); | }); | ||||||
|  | 
 | ||||||
|  | // TODO: remove soon
 | ||||||
|  | const vencordDir = "vencordDir" as keyof typeof Settings.store; | ||||||
|  | if (Settings.store[vencordDir]) { | ||||||
|  |     onceReady.then(() => | ||||||
|  |         setTimeout( | ||||||
|  |             () => | ||||||
|  |                 Alerts.show({ | ||||||
|  |                     title: "Custom Vencord Location", | ||||||
|  |                     body: "Due to security hardening changes in Vesktop, your custom Vencord location had to be reset. Please configure it again in the settings.", | ||||||
|  |                     onConfirm: () => delete Settings.store[vencordDir] | ||||||
|  |                 }), | ||||||
|  |             5000 | ||||||
|  |         ) | ||||||
|  |     ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -13,7 +13,7 @@ addPatch({ | ||||||
|             replacement: { |             replacement: { | ||||||
|                 // FIXME: fix eslint rule
 |                 // FIXME: fix eslint rule
 | ||||||
|                 // eslint-disable-next-line no-useless-escape
 |                 // eslint-disable-next-line no-useless-escape
 | ||||||
|                 match: /\.isPlatformEmbedded(?=\?\i\.DesktopNotificationTypes\.ALL)/g, |                 match: /\.isPlatformEmbedded(?=\?\i\.\i\.ALL)/g, | ||||||
|                 replace: "$&||true" |                 replace: "$&||true" | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -12,7 +12,7 @@ addPatch({ | ||||||
|             find: "lastOutputSystemDevice.justChanged", |             find: "lastOutputSystemDevice.justChanged", | ||||||
|             replacement: { |             replacement: { | ||||||
|                 // eslint-disable-next-line no-useless-escape
 |                 // eslint-disable-next-line no-useless-escape
 | ||||||
|                 match: /(\i)\.default\.getState\(\).neverShowModal/, |                 match: /(\i)\.\i\.getState\(\).neverShowModal/, | ||||||
|                 replace: "$& || $self.shouldIgnore($1)" |                 replace: "$& || $self.shouldIgnore($1)" | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -36,7 +36,7 @@ if (isLinux) { | ||||||
| 
 | 
 | ||||||
|         const constraints = { |         const constraints = { | ||||||
|             ...track.getConstraints(), |             ...track.getConstraints(), | ||||||
|             frameRate, |             frameRate: { min: frameRate, ideal: frameRate }, | ||||||
|             width: { min: 640, ideal: width, max: width }, |             width: { min: 640, ideal: width, max: width }, | ||||||
|             height: { min: 480, ideal: height, max: height }, |             height: { min: 480, ideal: height, max: height }, | ||||||
|             advanced: [{ width: width, height: height }], |             advanced: [{ width: width, height: height }], | ||||||
|  |  | ||||||
|  | @ -6,7 +6,8 @@ | ||||||
| 
 | 
 | ||||||
| import { addContextMenuPatch } from "@vencord/types/api/ContextMenu"; | import { addContextMenuPatch } from "@vencord/types/api/ContextMenu"; | ||||||
| import { findStoreLazy } from "@vencord/types/webpack"; | import { findStoreLazy } from "@vencord/types/webpack"; | ||||||
| import { FluxDispatcher, Menu, useStateFromStores } from "@vencord/types/webpack/common"; | import { FluxDispatcher, Menu, useMemo, useStateFromStores } from "@vencord/types/webpack/common"; | ||||||
|  | import { useSettings } from "renderer/settings"; | ||||||
| 
 | 
 | ||||||
| import { addPatch } from "./shared"; | import { addPatch } from "./shared"; | ||||||
| 
 | 
 | ||||||
|  | @ -50,7 +51,16 @@ addContextMenuPatch("textarea-context", children => { | ||||||
|     const spellCheckEnabled = useStateFromStores([SpellCheckStore], () => SpellCheckStore.isEnabled()); |     const spellCheckEnabled = useStateFromStores([SpellCheckStore], () => SpellCheckStore.isEnabled()); | ||||||
|     const hasCorrections = Boolean(word && corrections?.length); |     const hasCorrections = Boolean(word && corrections?.length); | ||||||
| 
 | 
 | ||||||
|     children.push( |     const availableLanguages = useMemo(VesktopNative.spellcheck.getAvailableLanguages, []); | ||||||
|  | 
 | ||||||
|  |     const settings = useSettings(); | ||||||
|  |     const spellCheckLanguages = (settings.spellCheckLanguages ??= [...new Set(navigator.languages)]); | ||||||
|  | 
 | ||||||
|  |     const pasteSectionIndex = children.findIndex(c => c?.props?.children?.some(c => c?.props?.id === "paste")); | ||||||
|  | 
 | ||||||
|  |     children.splice( | ||||||
|  |         pasteSectionIndex === -1 ? children.length : pasteSectionIndex, | ||||||
|  |         0, | ||||||
|         <Menu.MenuGroup> |         <Menu.MenuGroup> | ||||||
|             {hasCorrections && ( |             {hasCorrections && ( | ||||||
|                 <> |                 <> | ||||||
|  | @ -69,14 +79,39 @@ addContextMenuPatch("textarea-context", children => { | ||||||
|                     /> |                     /> | ||||||
|                 </> |                 </> | ||||||
|             )} |             )} | ||||||
|             <Menu.MenuCheckboxItem | 
 | ||||||
|                 id="vcd-spellcheck-enabled" |             <Menu.MenuItem id="vcd-spellcheck-settings" label="Spellcheck Settings"> | ||||||
|                 label="Enable Spellcheck" |                 <Menu.MenuCheckboxItem | ||||||
|                 checked={spellCheckEnabled} |                     id="vcd-spellcheck-enabled" | ||||||
|                 action={() => { |                     label="Enable Spellcheck" | ||||||
|                     FluxDispatcher.dispatch({ type: "SPELLCHECK_TOGGLE" }); |                     checked={spellCheckEnabled} | ||||||
|                 }} |                     action={() => { | ||||||
|             /> |                         FluxDispatcher.dispatch({ type: "SPELLCHECK_TOGGLE" }); | ||||||
|  |                     }} | ||||||
|  |                 /> | ||||||
|  | 
 | ||||||
|  |                 <Menu.MenuItem id="vcd-spellcheck-languages" label="Languages" disabled={!spellCheckEnabled}> | ||||||
|  |                     {availableLanguages.map(lang => { | ||||||
|  |                         const isEnabled = spellCheckLanguages.includes(lang); | ||||||
|  |                         return ( | ||||||
|  |                             <Menu.MenuCheckboxItem | ||||||
|  |                                 id={"vcd-spellcheck-lang-" + lang} | ||||||
|  |                                 label={lang} | ||||||
|  |                                 checked={isEnabled} | ||||||
|  |                                 disabled={!isEnabled && spellCheckLanguages.length >= 5} | ||||||
|  |                                 action={() => { | ||||||
|  |                                     const newSpellCheckLanguages = spellCheckLanguages.filter(l => l !== lang); | ||||||
|  |                                     if (newSpellCheckLanguages.length === spellCheckLanguages.length) { | ||||||
|  |                                         newSpellCheckLanguages.push(lang); | ||||||
|  |                                     } | ||||||
|  | 
 | ||||||
|  |                                     settings.spellCheckLanguages = newSpellCheckLanguages; | ||||||
|  |                                 }} | ||||||
|  |                             /> | ||||||
|  |                         ); | ||||||
|  |                     })} | ||||||
|  |                 </Menu.MenuItem> | ||||||
|  |             </Menu.MenuItem> | ||||||
|         </Menu.MenuGroup> |         </Menu.MenuGroup> | ||||||
|     ); |     ); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -23,13 +23,14 @@ export const enum IpcEvents { | ||||||
|     GET_SETTINGS = "VCD_GET_SETTINGS", |     GET_SETTINGS = "VCD_GET_SETTINGS", | ||||||
|     SET_SETTINGS = "VCD_SET_SETTINGS", |     SET_SETTINGS = "VCD_SET_SETTINGS", | ||||||
| 
 | 
 | ||||||
|  |     GET_VENCORD_DIR = "VCD_GET_VENCORD_DIR", | ||||||
|     SELECT_VENCORD_DIR = "VCD_SELECT_VENCORD_DIR", |     SELECT_VENCORD_DIR = "VCD_SELECT_VENCORD_DIR", | ||||||
| 
 | 
 | ||||||
|     UPDATER_GET_DATA = "VCD_UPDATER_GET_DATA", |     UPDATER_GET_DATA = "VCD_UPDATER_GET_DATA", | ||||||
|     UPDATER_DOWNLOAD = "VCD_UPDATER_DOWNLOAD", |     UPDATER_DOWNLOAD = "VCD_UPDATER_DOWNLOAD", | ||||||
|     UPDATE_IGNORE = "VCD_UPDATE_IGNORE", |     UPDATE_IGNORE = "VCD_UPDATE_IGNORE", | ||||||
| 
 | 
 | ||||||
|     SPELLCHECK_SET_LANGUAGES = "VCD_SPELLCHECK_SET_LANGUAGES", |     SPELLCHECK_GET_AVAILABLE_LANGUAGES = "VCD_SPELLCHECK_GET_AVAILABLE_LANGUAGES", | ||||||
|     SPELLCHECK_RESULT = "VCD_SPELLCHECK_RESULT", |     SPELLCHECK_RESULT = "VCD_SPELLCHECK_RESULT", | ||||||
|     SPELLCHECK_REPLACE_MISSPELLING = "VCD_SPELLCHECK_REPLACE_MISSPELLING", |     SPELLCHECK_REPLACE_MISSPELLING = "VCD_SPELLCHECK_REPLACE_MISSPELLING", | ||||||
|     SPELLCHECK_ADD_TO_DICTIONARY = "VCD_SPELLCHECK_ADD_TO_DICTIONARY", |     SPELLCHECK_ADD_TO_DICTIONARY = "VCD_SPELLCHECK_ADD_TO_DICTIONARY", | ||||||
|  |  | ||||||
							
								
								
									
										24
									
								
								src/shared/settings.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										24
									
								
								src/shared/settings.d.ts
									
									
									
									
										vendored
									
									
								
							|  | @ -8,7 +8,6 @@ import type { Rectangle } from "electron"; | ||||||
| 
 | 
 | ||||||
| export interface Settings { | export interface Settings { | ||||||
|     discordBranch?: "stable" | "canary" | "ptb"; |     discordBranch?: "stable" | "canary" | "ptb"; | ||||||
|     vencordDir?: string; |  | ||||||
|     transparencyOption?: "none" | "mica" | "tabbed" | "acrylic"; |     transparencyOption?: "none" | "mica" | "tabbed" | "acrylic"; | ||||||
|     tray?: boolean; |     tray?: boolean; | ||||||
|     trayBadge?: boolean; |     trayBadge?: boolean; | ||||||
|  | @ -22,15 +21,27 @@ export interface Settings { | ||||||
|     appBadge?: boolean; |     appBadge?: boolean; | ||||||
|     disableMinSize?: boolean; |     disableMinSize?: boolean; | ||||||
|     clickTrayToShowHide?: boolean; |     clickTrayToShowHide?: boolean; | ||||||
|     /** @deprecated use customTitleBar */ |  | ||||||
|     discordWindowsTitleBar?: boolean; |  | ||||||
|     customTitleBar?: boolean; |     customTitleBar?: boolean; | ||||||
| 
 | 
 | ||||||
|     checkUpdates?: boolean; |  | ||||||
| 
 |  | ||||||
|     splashTheming?: boolean; |     splashTheming?: boolean; | ||||||
|     splashColor?: string; |     splashColor?: string; | ||||||
|     splashBackground?: string; |     splashBackground?: string; | ||||||
|  | 
 | ||||||
|  |     spellCheckLanguages?: string[]; | ||||||
|  | 
 | ||||||
|  |     audio?: { | ||||||
|  |         workaround?: boolean; | ||||||
|  | 
 | ||||||
|  |         deviceSelect?: boolean; | ||||||
|  |         granularSelect?: boolean; | ||||||
|  | 
 | ||||||
|  |         ignoreVirtual?: boolean; | ||||||
|  |         ignoreDevices?: boolean; | ||||||
|  |         ignoreInputMedia?: boolean; | ||||||
|  | 
 | ||||||
|  |         onlySpeakers?: boolean; | ||||||
|  |         onlyDefaultSpeakers?: boolean; | ||||||
|  |     }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export interface State { | export interface State { | ||||||
|  | @ -39,8 +50,9 @@ export interface State { | ||||||
|     windowBounds?: Rectangle; |     windowBounds?: Rectangle; | ||||||
|     displayid: int; |     displayid: int; | ||||||
| 
 | 
 | ||||||
|     skippedUpdate?: string; |  | ||||||
|     firstLaunch?: boolean; |     firstLaunch?: boolean; | ||||||
| 
 | 
 | ||||||
|     steamOSLayoutVersion?: number; |     steamOSLayoutVersion?: number; | ||||||
|  | 
 | ||||||
|  |     vencordDir?: string; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -59,6 +59,19 @@ export class SettingsStore<T extends object> { | ||||||
|                 self.pathListeners.get(setPath)?.forEach(cb => cb(value)); |                 self.pathListeners.get(setPath)?.forEach(cb => cb(value)); | ||||||
| 
 | 
 | ||||||
|                 return true; |                 return true; | ||||||
|  |             }, | ||||||
|  |             deleteProperty(target, key: string) { | ||||||
|  |                 if (!(key in target)) return true; | ||||||
|  | 
 | ||||||
|  |                 const res = Reflect.deleteProperty(target, key); | ||||||
|  |                 if (!res) return false; | ||||||
|  | 
 | ||||||
|  |                 const setPath = `${path}${path && "."}${key}`; | ||||||
|  | 
 | ||||||
|  |                 self.globalListeners.forEach(cb => cb(root, setPath)); | ||||||
|  |                 self.pathListeners.get(setPath)?.forEach(cb => cb(undefined)); | ||||||
|  | 
 | ||||||
|  |                 return res; | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -1,119 +0,0 @@ | ||||||
| /* |  | ||||||
|  * SPDX-License-Identifier: GPL-3.0 |  | ||||||
|  * Vesktop, a desktop app aiming to give you a snappier Discord Experience |  | ||||||
|  * Copyright (c) 2023 Vendicated and Vencord contributors |  | ||||||
|  */ |  | ||||||
| 
 |  | ||||||
| import { app, BrowserWindow, shell } from "electron"; |  | ||||||
| import { Settings, State } from "main/settings"; |  | ||||||
| import { handle } from "main/utils/ipcWrappers"; |  | ||||||
| import { makeLinksOpenExternally } from "main/utils/makeLinksOpenExternally"; |  | ||||||
| import { githubGet, ReleaseData } from "main/utils/vencordLoader"; |  | ||||||
| import { join } from "path"; |  | ||||||
| import { IpcEvents } from "shared/IpcEvents"; |  | ||||||
| import { ICON_PATH, VIEW_DIR } from "shared/paths"; |  | ||||||
| 
 |  | ||||||
| export interface UpdateData { |  | ||||||
|     currentVersion: string; |  | ||||||
|     latestVersion: string; |  | ||||||
|     release: ReleaseData; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| let updateData: UpdateData; |  | ||||||
| 
 |  | ||||||
| handle(IpcEvents.UPDATER_GET_DATA, () => updateData); |  | ||||||
| handle(IpcEvents.UPDATER_DOWNLOAD, () => { |  | ||||||
|     const portable = !!process.env.PORTABLE_EXECUTABLE_FILE; |  | ||||||
| 
 |  | ||||||
|     const { assets } = updateData.release; |  | ||||||
|     const url = (() => { |  | ||||||
|         switch (process.platform) { |  | ||||||
|             case "win32": |  | ||||||
|                 return assets.find(a => { |  | ||||||
|                     if (!a.name.endsWith(".exe")) return false; |  | ||||||
| 
 |  | ||||||
|                     const isSetup = a.name.includes("Setup"); |  | ||||||
|                     return portable ? !isSetup : isSetup; |  | ||||||
|                 })!.browser_download_url; |  | ||||||
|             case "darwin": |  | ||||||
|                 return assets.find(a => |  | ||||||
|                     process.arch === "arm64" |  | ||||||
|                         ? a.name.endsWith("-arm64-mac.zip") |  | ||||||
|                         : a.name.endsWith("-mac.zip") && !a.name.includes("arm64") |  | ||||||
|                 )!.browser_download_url; |  | ||||||
|             case "linux": |  | ||||||
|                 return updateData.release.html_url; |  | ||||||
|             default: |  | ||||||
|                 throw new Error(`Unsupported platform: ${process.platform}`); |  | ||||||
|         } |  | ||||||
|     })(); |  | ||||||
| 
 |  | ||||||
|     shell.openExternal(url); |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| handle(IpcEvents.UPDATE_IGNORE, () => { |  | ||||||
|     State.store.skippedUpdate = updateData.latestVersion; |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| function isOutdated(oldVersion: string, newVersion: string) { |  | ||||||
|     const oldParts = oldVersion.split("."); |  | ||||||
|     const newParts = newVersion.split("."); |  | ||||||
| 
 |  | ||||||
|     if (oldParts.length !== newParts.length) |  | ||||||
|         throw new Error(`Incompatible version strings (old: ${oldVersion}, new: ${newVersion})`); |  | ||||||
| 
 |  | ||||||
|     for (let i = 0; i < oldParts.length; i++) { |  | ||||||
|         const oldPart = Number(oldParts[i]); |  | ||||||
|         const newPart = Number(newParts[i]); |  | ||||||
| 
 |  | ||||||
|         if (isNaN(oldPart) || isNaN(newPart)) |  | ||||||
|             throw new Error(`Invalid version string (old: ${oldVersion}, new: ${newVersion})`); |  | ||||||
| 
 |  | ||||||
|         if (oldPart < newPart) return true; |  | ||||||
|         if (oldPart > newPart) return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return false; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export async function checkUpdates() { |  | ||||||
|     if (Settings.store.checkUpdates === false) return; |  | ||||||
| 
 |  | ||||||
|     try { |  | ||||||
|         const raw = await githubGet("/repos/Vencord/Vesktop/releases/latest"); |  | ||||||
|         const data: ReleaseData = await raw.json(); |  | ||||||
| 
 |  | ||||||
|         const oldVersion = app.getVersion(); |  | ||||||
|         const newVersion = data.tag_name.replace(/^v/, ""); |  | ||||||
|         updateData = { |  | ||||||
|             currentVersion: oldVersion, |  | ||||||
|             latestVersion: newVersion, |  | ||||||
|             release: data |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         if (State.store.skippedUpdate !== newVersion && isOutdated(oldVersion, newVersion)) { |  | ||||||
|             openNewUpdateWindow(); |  | ||||||
|         } |  | ||||||
|     } catch (e) { |  | ||||||
|         console.error("AppUpdater: Failed to check for updates\n", e); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function openNewUpdateWindow() { |  | ||||||
|     const win = new BrowserWindow({ |  | ||||||
|         width: 500, |  | ||||||
|         autoHideMenuBar: true, |  | ||||||
|         alwaysOnTop: true, |  | ||||||
|         webPreferences: { |  | ||||||
|             preload: join(__dirname, "updaterPreload.js"), |  | ||||||
|             nodeIntegration: false, |  | ||||||
|             contextIsolation: true, |  | ||||||
|             sandbox: true |  | ||||||
|         }, |  | ||||||
|         icon: ICON_PATH |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     makeLinksOpenExternally(win); |  | ||||||
| 
 |  | ||||||
|     win.loadFile(join(VIEW_DIR, "updater.html")); |  | ||||||
| } |  | ||||||
|  | @ -1,21 +0,0 @@ | ||||||
| /* |  | ||||||
|  * SPDX-License-Identifier: GPL-3.0 |  | ||||||
|  * Vesktop, a desktop app aiming to give you a snappier Discord Experience |  | ||||||
|  * Copyright (c) 2023 Vendicated and Vencord contributors |  | ||||||
|  */ |  | ||||||
| 
 |  | ||||||
| import { contextBridge } from "electron"; |  | ||||||
| import { invoke } from "preload/typedIpc"; |  | ||||||
| import { IpcEvents } from "shared/IpcEvents"; |  | ||||||
| 
 |  | ||||||
| import type { UpdateData } from "./main"; |  | ||||||
| 
 |  | ||||||
| contextBridge.exposeInMainWorld("Updater", { |  | ||||||
|     getData: () => invoke<UpdateData>(IpcEvents.UPDATER_GET_DATA), |  | ||||||
|     download: () => { |  | ||||||
|         invoke<void>(IpcEvents.UPDATER_DOWNLOAD); |  | ||||||
|         invoke<void>(IpcEvents.CLOSE); |  | ||||||
|     }, |  | ||||||
|     ignore: () => invoke<void>(IpcEvents.UPDATE_IGNORE), |  | ||||||
|     close: () => invoke<void>(IpcEvents.CLOSE) |  | ||||||
| }); |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 Albert Zhang
						Albert Zhang