diff --git a/README.md b/README.md index 967d1c2..5da8894 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Vesktop is a custom Discord desktop app ## Installing -Visit https://vesktop.vencord.dev/install +Visit https://vesktop.dev/install ## Building from Source @@ -47,3 +47,14 @@ pnpm package --linux pacman # Or package to a directory only pnpm package:dir ``` + +## Building LibVesktop from Source + +This is a small C++ helper library Vesktop uses on Linux to emit D-Bus events. By default, prebuilt binaries for x64 and arm64 are used. + +If you want to build it from source: +1. Install build dependencies: + - Debian/Ubuntu: `apt install build-essential python3 curl pkg-config libglib2.0-dev` + - Fedora: `dnf install @c-development @development-tools python3 curl pkgconf-pkg-config glib2-devel` +2. Run `pnpm buildLibVesktop` +3. From now on, building Vesktop will use your own build \ No newline at end of file diff --git a/package.json b/package.json index 02f41d3..e3ef779 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "scripts": { "build": "tsx scripts/build/build.mts", "build:dev": "pnpm build --dev", + "buildLibVesktop": "pnpm -C packages/libvesktop install && pnpm -C packages/libvesktop run build", "package": "pnpm build && electron-builder", "package:dir": "pnpm build && electron-builder --dir", "lint": "eslint", @@ -49,6 +50,7 @@ "eslint-plugin-simple-header": "^1.2.2", "eslint-plugin-simple-import-sort": "^12.1.1", "eslint-plugin-unused-imports": "^4.2.0", + "libvesktop": "link:packages/libvesktop", "prettier": "^3.6.2", "source-map-support": "^0.5.21", "tsx": "^4.20.6", diff --git a/packages/libvesktop/.gitignore b/packages/libvesktop/.gitignore new file mode 100644 index 0000000..c795b05 --- /dev/null +++ b/packages/libvesktop/.gitignore @@ -0,0 +1 @@ +build \ No newline at end of file diff --git a/packages/libvesktop/Dockerfile b/packages/libvesktop/Dockerfile new file mode 100644 index 0000000..864ff26 --- /dev/null +++ b/packages/libvesktop/Dockerfile @@ -0,0 +1,19 @@ +# Dockerfile for building both x64 and arm64 on an old distro for maximum compatibility. + +# ubuntu20 is dead but debian11 is still supported for now +FROM debian:11 +ENV DEBIAN_FRONTEND=noninteractive + +RUN dpkg --add-architecture arm64 + +RUN apt-get update && apt-get install -y \ + build-essential python3 curl pkg-config \ + g++-aarch64-linux-gnu libglib2.0-dev:amd64 libglib2.0-dev:arm64 \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ + && apt-get update && apt-get install -y nodejs \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /src diff --git a/packages/libvesktop/binding.gyp b/packages/libvesktop/binding.gyp new file mode 100644 index 0000000..685aadf --- /dev/null +++ b/packages/libvesktop/binding.gyp @@ -0,0 +1,19 @@ +{ + "targets": [ + { + "target_name": "libvesktop", + "sources": [ "src/libvesktop.cc" ], + "include_dirs": [ + "=12'} + + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + + '@npmcli/agent@3.0.0': + resolution: {integrity: sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==} + engines: {node: ^18.17.0 || >=20.5.0} + + '@npmcli/fs@4.0.0': + resolution: {integrity: sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==} + engines: {node: ^18.17.0 || >=20.5.0} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + abbrev@3.0.1: + resolution: {integrity: sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==} + engines: {node: ^18.17.0 || >=20.5.0} + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + cacache@19.0.1: + resolution: {integrity: sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==} + engines: {node: ^18.17.0 || >=20.5.0} + + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + encoding@0.1.13: + resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + err-code@2.0.3: + resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} + + exponential-backoff@3.1.2: + resolution: {integrity: sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + fs-minipass@3.0.3: + resolution: {integrity: sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + ip-address@10.0.1: + resolution: {integrity: sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==} + engines: {node: '>= 12'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isexe@3.1.1: + resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} + engines: {node: '>=16'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + make-fetch-happen@14.0.3: + resolution: {integrity: sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==} + engines: {node: ^18.17.0 || >=20.5.0} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass-collect@2.0.1: + resolution: {integrity: sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass-fetch@4.0.1: + resolution: {integrity: sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==} + engines: {node: ^18.17.0 || >=20.5.0} + + minipass-flush@1.0.5: + resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} + engines: {node: '>= 8'} + + minipass-pipeline@1.2.4: + resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} + engines: {node: '>=8'} + + minipass-sized@1.0.3: + resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} + engines: {node: '>=8'} + + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@3.1.0: + resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} + engines: {node: '>= 18'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + + node-addon-api@8.5.0: + resolution: {integrity: sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==} + engines: {node: ^18 || ^20 || >= 21} + + node-gyp@11.4.2: + resolution: {integrity: sha512-3gD+6zsrLQH7DyYOUIutaauuXrcyxeTPyQuZQCQoNPZMHMMS5m4y0xclNpvYzoK3VNzuyxT6eF4mkIL4WSZ1eQ==} + engines: {node: ^18.17.0 || >=20.5.0} + hasBin: true + + nopt@8.1.0: + resolution: {integrity: sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==} + engines: {node: ^18.17.0 || >=20.5.0} + hasBin: true + + p-map@7.0.3: + resolution: {integrity: sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==} + engines: {node: '>=18'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + proc-log@5.0.0: + resolution: {integrity: sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==} + engines: {node: ^18.17.0 || >=20.5.0} + + promise-retry@2.0.1: + resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} + engines: {node: '>=10'} + + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + + socks-proxy-agent@8.0.5: + resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} + engines: {node: '>= 14'} + + socks@2.8.7: + resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + + ssri@12.0.0: + resolution: {integrity: sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==} + engines: {node: ^18.17.0 || >=20.5.0} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + engines: {node: '>=12'} + + tar@7.5.1: + resolution: {integrity: sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==} + engines: {node: '>=18'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + unique-filename@4.0.0: + resolution: {integrity: sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==} + engines: {node: ^18.17.0 || >=20.5.0} + + unique-slug@5.0.0: + resolution: {integrity: sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==} + engines: {node: ^18.17.0 || >=20.5.0} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + which@5.0.0: + resolution: {integrity: sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==} + engines: {node: ^18.17.0 || >=20.5.0} + hasBin: true + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + +snapshots: + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.2 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.2 + + '@npmcli/agent@3.0.0': + dependencies: + agent-base: 7.1.4 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + lru-cache: 10.4.3 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + '@npmcli/fs@4.0.0': + dependencies: + semver: 7.7.2 + + '@pkgjs/parseargs@0.11.0': + optional: true + + abbrev@3.0.1: {} + + agent-base@7.1.4: {} + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + balanced-match@1.0.2: {} + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + cacache@19.0.1: + dependencies: + '@npmcli/fs': 4.0.0 + fs-minipass: 3.0.3 + glob: 10.4.5 + lru-cache: 10.4.3 + minipass: 7.1.2 + minipass-collect: 2.0.1 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + p-map: 7.0.3 + ssri: 12.0.0 + tar: 7.5.1 + unique-filename: 4.0.0 + + chownr@3.0.0: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + eastasianwidth@0.2.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + encoding@0.1.13: + dependencies: + iconv-lite: 0.6.3 + optional: true + + env-paths@2.2.1: {} + + err-code@2.0.3: {} + + exponential-backoff@3.1.2: {} + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + fs-minipass@3.0.3: + dependencies: + minipass: 7.1.2 + + glob@10.4.5: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + graceful-fs@4.2.11: {} + + http-cache-semantics@4.2.0: {} + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + optional: true + + imurmurhash@0.1.4: {} + + ip-address@10.0.1: {} + + is-fullwidth-code-point@3.0.0: {} + + isexe@2.0.0: {} + + isexe@3.1.1: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + lru-cache@10.4.3: {} + + make-fetch-happen@14.0.3: + dependencies: + '@npmcli/agent': 3.0.0 + cacache: 19.0.1 + http-cache-semantics: 4.2.0 + minipass: 7.1.2 + minipass-fetch: 4.0.1 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + negotiator: 1.0.0 + proc-log: 5.0.0 + promise-retry: 2.0.1 + ssri: 12.0.0 + transitivePeerDependencies: + - supports-color + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minipass-collect@2.0.1: + dependencies: + minipass: 7.1.2 + + minipass-fetch@4.0.1: + dependencies: + minipass: 7.1.2 + minipass-sized: 1.0.3 + minizlib: 3.1.0 + optionalDependencies: + encoding: 0.1.13 + + minipass-flush@1.0.5: + dependencies: + minipass: 3.3.6 + + minipass-pipeline@1.2.4: + dependencies: + minipass: 3.3.6 + + minipass-sized@1.0.3: + dependencies: + minipass: 3.3.6 + + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + + minipass@7.1.2: {} + + minizlib@3.1.0: + dependencies: + minipass: 7.1.2 + + ms@2.1.3: {} + + negotiator@1.0.0: {} + + node-addon-api@8.5.0: {} + + node-gyp@11.4.2: + dependencies: + env-paths: 2.2.1 + exponential-backoff: 3.1.2 + graceful-fs: 4.2.11 + make-fetch-happen: 14.0.3 + nopt: 8.1.0 + proc-log: 5.0.0 + semver: 7.7.2 + tar: 7.5.1 + tinyglobby: 0.2.15 + which: 5.0.0 + transitivePeerDependencies: + - supports-color + + nopt@8.1.0: + dependencies: + abbrev: 3.0.1 + + p-map@7.0.3: {} + + package-json-from-dist@1.0.1: {} + + path-key@3.1.1: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + picomatch@4.0.3: {} + + proc-log@5.0.0: {} + + promise-retry@2.0.1: + dependencies: + err-code: 2.0.3 + retry: 0.12.0 + + retry@0.12.0: {} + + safer-buffer@2.1.2: + optional: true + + semver@7.7.2: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + signal-exit@4.1.0: {} + + smart-buffer@4.2.0: {} + + socks-proxy-agent@8.0.5: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + socks: 2.8.7 + transitivePeerDependencies: + - supports-color + + socks@2.8.7: + dependencies: + ip-address: 10.0.1 + smart-buffer: 4.2.0 + + ssri@12.0.0: + dependencies: + minipass: 7.1.2 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.2 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + + tar@7.5.1: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.1.0 + yallist: 5.0.0 + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + unique-filename@4.0.0: + dependencies: + unique-slug: 5.0.0 + + unique-slug@5.0.0: + dependencies: + imurmurhash: 0.1.4 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + which@5.0.0: + dependencies: + isexe: 3.1.1 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.1.2 + + yallist@4.0.0: {} + + yallist@5.0.0: {} diff --git a/packages/libvesktop/prebuilds/vesktop-arm64.node b/packages/libvesktop/prebuilds/vesktop-arm64.node new file mode 100755 index 0000000..ba30187 Binary files /dev/null and b/packages/libvesktop/prebuilds/vesktop-arm64.node differ diff --git a/packages/libvesktop/prebuilds/vesktop-x64.node b/packages/libvesktop/prebuilds/vesktop-x64.node new file mode 100755 index 0000000..843d499 Binary files /dev/null and b/packages/libvesktop/prebuilds/vesktop-x64.node differ diff --git a/packages/libvesktop/src/libvesktop.cc b/packages/libvesktop/src/libvesktop.cc new file mode 100644 index 0000000..8222bb3 --- /dev/null +++ b/packages/libvesktop/src/libvesktop.cc @@ -0,0 +1,269 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +template +struct GObjectDeleter +{ + void operator()(T *obj) const + { + if (obj) + g_object_unref(obj); + } +}; + +template +using GObjectPtr = std::unique_ptr>; + +struct GVariantDeleter +{ + void operator()(GVariant *variant) const + { + if (variant) + g_variant_unref(variant); + } +}; + +using GVariantPtr = std::unique_ptr; + +struct GErrorDeleter +{ + void operator()(GError *error) const + { + if (error) + g_error_free(error); + } +}; + +using GErrorPtr = std::unique_ptr; + +bool update_launcher_count(int count) +{ + GError *error = nullptr; + + const char *chromeDesktop = std::getenv("CHROME_DESKTOP"); + std::string desktop_id = std::string("application://") + (chromeDesktop ? chromeDesktop : "vesktop.desktop"); + + GObjectPtr bus(g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, &error)); + if (!bus) + { + GErrorPtr error_ptr(error); + std::cerr << "[libvesktop::update_launcher_count] Failed to connect to session bus: " + << (error_ptr ? error_ptr->message : "unknown error") << std::endl; + return false; + } + + GVariantBuilder builder; + g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}")); + g_variant_builder_add(&builder, "{sv}", "count", g_variant_new_int64(count)); + g_variant_builder_add(&builder, "{sv}", "count-visible", g_variant_new_boolean(count != 0)); + + gboolean result = g_dbus_connection_emit_signal( + bus.get(), + nullptr, + "/", + "com.canonical.Unity.LauncherEntry", + "Update", + g_variant_new("(sa{sv})", desktop_id.c_str(), &builder), + &error); + + if (!result || error) + { + GErrorPtr error_ptr(error); + std::cerr << "[libvesktop::update_launcher_count] Failed to emit Update signal: " + << (error_ptr ? error_ptr->message : "unknown error") << std::endl; + return false; + } + + return true; +} + +std::optional get_accent_color() +{ + GError *error = nullptr; + + GObjectPtr bus(g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, &error)); + if (!bus) + { + GErrorPtr error_ptr(error); + std::cerr << "[libvesktop::get_accent_color] Failed to connect to session bus: " + << (error_ptr ? error_ptr->message : "unknown error") << std::endl; + return std::nullopt; + } + + GVariantPtr reply(g_dbus_connection_call_sync( + bus.get(), + "org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.Settings", + "Read", + g_variant_new("(ss)", "org.freedesktop.appearance", "accent-color"), + nullptr, + G_DBUS_CALL_FLAGS_NONE, + 5000, + nullptr, + &error)); + + if (!reply) + { + GErrorPtr error_ptr(error); + std::cerr << "[libvesktop::get_accent_color] Failed to call Read: " + << (error_ptr ? error_ptr->message : "unknown error") << std::endl; + return std::nullopt; + } + + GVariant *inner_raw = nullptr; + g_variant_get(reply.get(), "(v)", &inner_raw); + if (!inner_raw) + { + std::cerr << "[libvesktop::get_accent_color] Inner variant is null" << std::endl; + return std::nullopt; + } + + GVariantPtr inner(inner_raw); + + // Unwrap nested variants + while (g_variant_is_of_type(inner.get(), G_VARIANT_TYPE_VARIANT)) + { + GVariant *next = g_variant_get_variant(inner.get()); + inner.reset(next); + } + + if (!g_variant_is_of_type(inner.get(), G_VARIANT_TYPE_TUPLE) || + g_variant_n_children(inner.get()) < 3) + { + std::cerr << "[libvesktop::get_accent_color] Inner variant is not a tuple of 3 doubles" << std::endl; + return std::nullopt; + } + + double r = 0.0, g = 0.0, b = 0.0; + g_variant_get(inner.get(), "(ddd)", &r, &g, &b); + + bool discard = false; + auto toInt = [&discard](double v) -> int + { + if (!std::isfinite(v) || v < 0.0 || v > 1.0) + { + discard = true; + return 0; + } + + return static_cast(std::round(v * 255.0)); + }; + + int32_t rgb = (toInt(r) << 16) | (toInt(g) << 8) | toInt(b); + if (discard) + return std::nullopt; + + return rgb; +} + +bool request_background(bool autostart, const std::vector &commandline) +{ + GError *error = nullptr; + + GObjectPtr bus(g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, &error)); + if (!bus) + { + GErrorPtr error_ptr(error); + std::cerr << "[libvesktop::request_background] Failed to connect to session bus: " + << (error_ptr ? error_ptr->message : "unknown error") << std::endl; + return false; + } + + GVariantBuilder builder; + g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}")); + g_variant_builder_add(&builder, "{sv}", "autostart", g_variant_new_boolean(autostart)); + + if (!commandline.empty()) + { + GVariantBuilder cmd_builder; + g_variant_builder_init(&cmd_builder, G_VARIANT_TYPE("as")); + for (const auto &s : commandline) + g_variant_builder_add(&cmd_builder, "s", s.c_str()); + g_variant_builder_add(&builder, "{sv}", "commandline", g_variant_builder_end(&cmd_builder)); + } + + GVariantPtr reply(g_dbus_connection_call_sync( + bus.get(), + "org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.Background", + "RequestBackground", + g_variant_new("(sa{sv})", "", &builder), + nullptr, + G_DBUS_CALL_FLAGS_NONE, + 5000, + nullptr, + &error)); + + if (!reply) + { + GErrorPtr error_ptr(error); + std::cerr << "[libvesktop::request_background] Failed to call RequestBackground: " + << (error_ptr ? error_ptr->message : "unknown error") << std::endl; + return false; + } + + return true; +} + +Napi::Value updateUnityLauncherCount(Napi::CallbackInfo const &info) +{ + if (info.Length() < 1 || !info[0].IsNumber()) + { + Napi::TypeError::New(info.Env(), "Expected (number)").ThrowAsJavaScriptException(); + return info.Env().Undefined(); + } + + int count = info[0].As().Int32Value(); + bool success = update_launcher_count(count); + return Napi::Boolean::New(info.Env(), success); +} + +Napi::Value getAccentColor(const Napi::CallbackInfo &info) +{ + auto color = get_accent_color(); + if (color) + return Napi::Number::New(info.Env(), *color); + return info.Env().Null(); +} + +Napi::Value RequestBackground(const Napi::CallbackInfo &info) +{ + Napi::Env env = info.Env(); + + if (info.Length() < 2 || !info[0].IsBoolean() || !info[1].IsArray()) + { + Napi::TypeError::New(env, "Expected (boolean, string[])").ThrowAsJavaScriptException(); + return env.Null(); + } + + bool autostart = info[0].As(); + Napi::Array arr = info[1].As(); + std::vector commandline; + for (uint32_t i = 0; i < arr.Length(); i++) + { + Napi::Value v = arr.Get(i); + if (v.IsString()) + commandline.push_back(v.As().Utf8Value()); + } + + bool ok = request_background(autostart, commandline); + return Napi::Boolean::New(env, ok); +} + +Napi::Object Init(Napi::Env env, Napi::Object exports) +{ + exports.Set("updateUnityLauncherCount", Napi::Function::New(env, updateUnityLauncherCount)); + exports.Set("getAccentColor", Napi::Function::New(env, getAccentColor)); + exports.Set("requestBackground", Napi::Function::New(env, RequestBackground)); + return exports; +} + +NODE_API_MODULE(libvesktop, Init) diff --git a/packages/libvesktop/test.js b/packages/libvesktop/test.js new file mode 100644 index 0000000..c2c12e6 --- /dev/null +++ b/packages/libvesktop/test.js @@ -0,0 +1,22 @@ +/** + * @type {typeof import(".")} + */ +const libVesktop = require("."); +const test = require("node:test"); +const assert = require("node:assert/strict"); + +test("getAccentColor should return a number", () => { + const color = libVesktop.getAccentColor(); + assert.strictEqual(typeof color, "number"); +}); + +test("updateUnityLauncherCount should return true (success)", () => { + assert.strictEqual(libVesktop.updateUnityLauncherCount(5), true); + assert.strictEqual(libVesktop.updateUnityLauncherCount(0), true); + assert.strictEqual(libVesktop.updateUnityLauncherCount(10), true); +}); + +test("requestBackground should return true (success)", () => { + assert.strictEqual(libVesktop.requestBackground(true, ["bash"]), true); + assert.strictEqual(libVesktop.requestBackground(false, []), true); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 09cf8d2..73270b3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71,6 +71,9 @@ importers: eslint-plugin-unused-imports: specifier: ^4.2.0 version: 4.2.0(@typescript-eslint/eslint-plugin@8.46.0(@typescript-eslint/parser@8.46.0(eslint@9.37.0)(typescript@5.9.3))(eslint@9.37.0)(typescript@5.9.3))(eslint@9.37.0) + libvesktop: + specifier: link:packages/libvesktop + version: link:packages/libvesktop prettier: specifier: ^3.6.2 version: 3.6.2 diff --git a/scripts/build/build.mts b/scripts/build/build.mts index ee3f7ca..0f62ac6 100644 --- a/scripts/build/build.mts +++ b/scripts/build/build.mts @@ -25,6 +25,9 @@ const NodeCommonOpts: BuildOptions = { platform: "node", external: ["electron"], target: ["esnext"], + loader: { + ".node": "file" + }, define: { IS_DEV: JSON.stringify(isDev) } @@ -50,8 +53,29 @@ async function copyVenmic() { ]).catch(() => console.warn("Failed to copy venmic. Building without venmic support")); } +async function copyLibVesktop() { + if (process.platform !== "linux") return; + + try { + await copyFile( + "./packages/libvesktop/build/Release/vesktop.node", + `./static/dist/libvesktop-${process.arch}.node` + ); + console.log("Using local libvesktop build"); + } catch { + console.log( + "Using prebuilt libvesktop binaries. Run `pnpm buildLibVesktop` and build again to build from source - see README.md for more details" + ); + return Promise.all([ + copyFile("./packages/libvesktop/prebuilds/vesktop-x64.node", "./static/dist/libvesktop-x64.node"), + copyFile("./packages/libvesktop/prebuilds/vesktop-arm64.node", "./static/dist/libvesktop-arm64.node") + ]).catch(() => console.warn("Failed to copy libvesktop. Building without libvesktop support")); + } +} + await Promise.all([ copyVenmic(), + copyLibVesktop(), createContext({ ...NodeCommonOpts, entryPoints: ["src/main/index.ts"], diff --git a/src/main/appBadge.ts b/src/main/appBadge.ts index 969caf3..a6230f7 100644 --- a/src/main/appBadge.ts +++ b/src/main/appBadge.ts @@ -8,7 +8,9 @@ import { app, NativeImage, nativeImage } from "electron"; import { join } from "path"; import { BADGE_DIR } from "shared/paths"; +import { updateUnityLauncherCount } from "./dbus"; import { AppEvents } from "./events"; +import { mainWin } from "./mainWindow"; const imgCache = new Map(); function loadBadge(index: number) { @@ -33,7 +35,7 @@ export function setBadgeCount(count: number) { switch (process.platform) { case "linux": if (count === -1) count = 0; - app.setBadgeCount(count); + updateUnityLauncherCount(count); break; case "darwin": if (count === 0) { @@ -48,8 +50,6 @@ export function setBadgeCount(count: number) { lastIndex = index; - // circular import shenanigans - const { mainWin } = require("./mainWindow") as typeof import("./mainWindow"); mainWin.setOverlayIcon(index === null ? null : loadBadge(index), description); break; } diff --git a/src/main/autoStart.ts b/src/main/autoStart.ts index 1f4a955..cfb43e8 100644 --- a/src/main/autoStart.ts +++ b/src/main/autoStart.ts @@ -5,30 +5,32 @@ */ import { app } from "electron"; -import { existsSync, mkdirSync, renameSync, rmSync, writeFileSync } from "fs"; +import { existsSync, mkdirSync, rmSync, writeFileSync } from "fs"; import { join } from "path"; import { stripIndent } from "shared/utils/text"; +import { IS_FLATPAK } from "./constants"; +import { requestBackground } from "./dbus"; +import { Settings, State } from "./settings"; +import { escapeDesktopFileArgument } from "./utils/desktopFileEscape"; + interface AutoStart { isEnabled(): boolean; enable(): void; disable(): void; } -function makeAutoStartLinux(): AutoStart { +function getEscapedCommandLine() { + const args = process.argv.map(escapeDesktopFileArgument); + if (Settings.store.autoStartMinimized) args.push("--start-minimized"); + return args; +} + +function makeAutoStartLinuxDesktop(): AutoStart { const configDir = process.env.XDG_CONFIG_HOME || join(process.env.HOME!, ".config"); const dir = join(configDir, "autostart"); const file = join(dir, "vesktop.desktop"); - // IM STUPID - const legacyName = join(dir, "vencord.desktop"); - if (existsSync(legacyName)) renameSync(legacyName, file); - - // "Quoting must be done by enclosing the argument between double quotes and escaping the double quote character, - // backtick character ("`"), dollar sign ("$") and backslash character ("\") by preceding it with an additional backslash character" - // https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables - const commandLine = process.argv.map(arg => '"' + arg.replace(/["$`\\]/g, "\\$&") + '"').join(" "); - return { isEnabled: () => existsSync(file), enable() { @@ -37,7 +39,7 @@ function makeAutoStartLinux(): AutoStart { Type=Application Name=Vesktop Comment=Vesktop autostart script - Exec=${commandLine} + Exec=${getEscapedCommandLine().join(" ")} StartupNotify=false Terminal=false Icon=vesktop @@ -50,10 +52,49 @@ function makeAutoStartLinux(): AutoStart { }; } +function makeAutoStartLinuxPortal() { + return { + isEnabled: () => State.store.linuxAutoStartEnabled === true, + enable() { + const success = requestBackground(true, getEscapedCommandLine()); + if (success) { + State.store.linuxAutoStartEnabled = true; + } + return success; + }, + disable() { + const success = requestBackground(false, []); + if (success) { + State.store.linuxAutoStartEnabled = false; + } + return success; + } + }; +} + const autoStartWindowsMac: AutoStart = { isEnabled: () => app.getLoginItemSettings().openAtLogin, - enable: () => app.setLoginItemSettings({ openAtLogin: true }), + enable: () => + app.setLoginItemSettings({ + openAtLogin: true, + args: Settings.store.autoStartMinimized ? ["--start-minimized"] : [] + }), disable: () => app.setLoginItemSettings({ openAtLogin: false }) }; -export const autoStart = process.platform === "linux" ? makeAutoStartLinux() : autoStartWindowsMac; +// The portal call uses the app id by default, which is org.chromium.Chromium, even in packaged Vesktop. +// This leads to an autostart entry named "Chromium" instead of "Vesktop". +// Thus, only use the portal inside Flatpak, where the app is actually correct. +// Maybe there is a way to fix it outside of flatpak, but I couldn't figure it out. +export const autoStart = + process.platform !== "linux" + ? autoStartWindowsMac + : IS_FLATPAK + ? makeAutoStartLinuxPortal() + : makeAutoStartLinuxDesktop(); + +Settings.addChangeListener("autoStartMinimized", () => { + if (!autoStart.isEnabled()) return; + + autoStart.enable(); +}); diff --git a/src/main/constants.ts b/src/main/constants.ts index dcfc8fc..10b6f66 100644 --- a/src/main/constants.ts +++ b/src/main/constants.ts @@ -20,7 +20,7 @@ export const DATA_DIR = mkdirSync(DATA_DIR, { recursive: true }); -const SESSION_DATA_DIR = join(DATA_DIR, "sessionData"); +export const SESSION_DATA_DIR = join(DATA_DIR, "sessionData"); app.setPath("sessionData", SESSION_DATA_DIR); export const VENCORD_SETTINGS_DIR = join(DATA_DIR, "settings"); @@ -28,12 +28,6 @@ export const VENCORD_QUICKCSS_FILE = join(VENCORD_SETTINGS_DIR, "quickCss.css"); export const VENCORD_SETTINGS_FILE = join(VENCORD_SETTINGS_DIR, "settings.json"); export const VENCORD_THEMES_DIR = join(DATA_DIR, "themes"); -// needs to be inline require because of circular dependency -// as otherwise "DATA_DIR" (which is used by ./settings) will be uninitialised -export const VENCORD_FILES_DIR = - (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)`; // dimensions shamelessly stolen from Discord Desktop :3 @@ -57,3 +51,5 @@ export const enum MessageBoxChoice { Default, Cancel } + +export const IS_FLATPAK = process.env.FLATPAK_ID !== undefined; diff --git a/src/main/dbus.ts b/src/main/dbus.ts new file mode 100644 index 0000000..05b2578 --- /dev/null +++ b/src/main/dbus.ts @@ -0,0 +1,40 @@ +/* + * Vesktop, a desktop app aiming to give you a snappier Discord Experience + * Copyright (c) 2025 Vendicated and Vesktop contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { app } from "electron"; +import { join } from "path"; +import { STATIC_DIR } from "shared/paths"; + +let libVesktop: typeof import("libvesktop") | null = null; + +function loadLibVesktop() { + try { + if (!libVesktop) { + libVesktop = require(join(STATIC_DIR, `dist/libvesktop-${process.arch}.node`)); + } + } catch (e) { + console.error("Failed to load libvesktop:", e); + } + + return libVesktop; +} + +export function getAccentColor() { + return loadLibVesktop()?.getAccentColor() ?? null; +} + +export function updateUnityLauncherCount(count: number) { + const libVesktop = loadLibVesktop(); + if (!libVesktop) { + return app.setBadgeCount(count); + } + + return libVesktop.updateUnityLauncherCount(count); +} + +export function requestBackground(autoStart: boolean, commandLine: string[]) { + return loadLibVesktop()?.requestBackground(autoStart, commandLine) ?? false; +} diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 94eb671..a17bc9c 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -28,13 +28,14 @@ import { debounce } from "shared/utils/debounce"; import { IpcEvents } from "../shared/IpcEvents"; import { setBadgeCount } from "./appBadge"; import { autoStart } from "./autoStart"; -import { VENCORD_FILES_DIR, VENCORD_QUICKCSS_FILE, VENCORD_THEMES_DIR } from "./constants"; +import { VENCORD_QUICKCSS_FILE, VENCORD_THEMES_DIR } from "./constants"; import { mainWin } from "./mainWindow"; import { Settings, State } from "./settings"; import { handle, handleSync } from "./utils/ipcWrappers"; import { PopoutWindows } from "./utils/popout"; import { isDeckGameMode, showGamePage } from "./utils/steamOS"; import { isValidVencordInstall } from "./utils/vencordLoader"; +import { VENCORD_FILES_DIR } from "./vencordFilesDir"; handleSync(IpcEvents.GET_VENCORD_PRELOAD_FILE, () => join(VENCORD_FILES_DIR, "vencordDesktopPreload.js")); handleSync(IpcEvents.GET_VENCORD_RENDERER_SCRIPT, () => diff --git a/src/main/mainWindow.ts b/src/main/mainWindow.ts index 063ab29..a514917 100644 --- a/src/main/mainWindow.ts +++ b/src/main/mainWindow.ts @@ -22,7 +22,7 @@ import type { SettingsStore } from "shared/utils/SettingsStore"; import { createAboutWindow } from "./about"; import { initArRPC } from "./arrpc"; -import { BrowserUserAgent, DEFAULT_HEIGHT, DEFAULT_WIDTH, MIN_HEIGHT, MIN_WIDTH, VENCORD_FILES_DIR } from "./constants"; +import { BrowserUserAgent, DEFAULT_HEIGHT, DEFAULT_WIDTH, MIN_HEIGHT, MIN_WIDTH } from "./constants"; import { AppEvents } from "./events"; import { darwinURL } from "./index"; import { sendRendererCommand } from "./ipcCommands"; @@ -33,6 +33,7 @@ import { clearData } from "./utils/clearData"; import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally"; import { applyDeckKeyboardFix, askToApplySteamLayout, isDeckGameMode } from "./utils/steamOS"; import { downloadVencordFiles, ensureVencordFiles } from "./utils/vencordLoader"; +import { VENCORD_FILES_DIR } from "./vencordFilesDir"; let isQuitting = false; diff --git a/src/main/utils/desktopFileEscape.ts b/src/main/utils/desktopFileEscape.ts new file mode 100644 index 0000000..d41265b --- /dev/null +++ b/src/main/utils/desktopFileEscape.ts @@ -0,0 +1,56 @@ +/* + * Vesktop, a desktop app aiming to give you a snappier Discord Experience + * Copyright (c) 2025 Vendicated and Vesktop contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +// https://specifications.freedesktop.org/desktop-entry-spec/latest/exec-variables.html + +// "If an argument contains a reserved character the argument must be quoted." +const desktopFileReservedChars = new Set([ + " ", + "\t", + "\n", + '"', + "'", + "\\", + ">", + "<", + "~", + "|", + "&", + ";", + "$", + "*", + "?", + "#", + "(", + ")", + "`" +]); + +export function escapeDesktopFileArgument(arg: string) { + let needsQuoting = false; + let out = ""; + + for (const c of arg) { + if (desktopFileReservedChars.has(c)) { + // "Quoting must be done by enclosing the argument between double quotes" + needsQuoting = true; + // "and escaping the double quote character, backtick character ("`"), dollar sign ("$") + // and backslash character ("\") by preceding it with an additional backslash character" + if (c === '"' || c === "`" || c === "$" || c === "\\") { + out += "\\"; + } + } + + // "Literal percentage characters must be escaped as %%" + if (c === "%") { + out += "%%"; + } else { + out += c; + } + } + + return needsQuoting ? `"${out}"` : out; +} diff --git a/src/main/utils/vencordLoader.ts b/src/main/utils/vencordLoader.ts index 85b6112..25748b3 100644 --- a/src/main/utils/vencordLoader.ts +++ b/src/main/utils/vencordLoader.ts @@ -6,9 +6,10 @@ import { mkdirSync } from "fs"; import { access, constants as FsConstants, writeFile } from "fs/promises"; +import { VENCORD_FILES_DIR } from "main/vencordFilesDir"; import { join } from "path"; -import { USER_AGENT, VENCORD_FILES_DIR } from "../constants"; +import { USER_AGENT } from "../constants"; import { downloadFile, fetchie } from "./http"; const API_BASE = "https://api.github.com"; diff --git a/src/main/vencordFilesDir.ts b/src/main/vencordFilesDir.ts new file mode 100644 index 0000000..8ef9ab8 --- /dev/null +++ b/src/main/vencordFilesDir.ts @@ -0,0 +1,13 @@ +/* + * Vesktop, a desktop app aiming to give you a snappier Discord Experience + * Copyright (c) 2025 Vendicated and Vesktop contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { join } from "path"; + +import { SESSION_DATA_DIR } from "./constants"; +import { State } from "./settings"; + +// this is in a separate file to avoid circular dependencies +export const VENCORD_FILES_DIR = State.store.vencordDir || join(SESSION_DATA_DIR, "vencordFiles"); diff --git a/src/renderer/components/settings/AutoStartToggle.tsx b/src/renderer/components/settings/AutoStartToggle.tsx index 1563ca3..d99f2cd 100644 --- a/src/renderer/components/settings/AutoStartToggle.tsx +++ b/src/renderer/components/settings/AutoStartToggle.tsx @@ -9,18 +9,28 @@ import { useState } from "@vencord/types/webpack/common"; import { SettingsComponent } from "./Settings"; import { VesktopSettingsSwitch } from "./VesktopSettingsSwitch"; -export const AutoStartToggle: SettingsComponent = () => { +export const AutoStartToggle: SettingsComponent = ({ settings }) => { const [autoStartEnabled, setAutoStartEnabled] = useState(VesktopNative.autostart.isEnabled()); return ( - { - await VesktopNative.autostart[v ? "enable" : "disable"](); - setAutoStartEnabled(v); - }} - /> + <> + { + await VesktopNative.autostart[v ? "enable" : "disable"](); + setAutoStartEnabled(v); + }} + /> + + (settings.autoStartMinimized = v)} + disabled={!autoStartEnabled} + /> + ); }; diff --git a/src/shared/settings.d.ts b/src/shared/settings.d.ts index 21c140a..5d3263c 100644 --- a/src/shared/settings.d.ts +++ b/src/shared/settings.d.ts @@ -11,6 +11,7 @@ export interface Settings { transparencyOption?: "none" | "mica" | "tabbed" | "acrylic"; tray?: boolean; minimizeToTray?: boolean; + autoStartMinimized?: boolean; openLinksWithElectron?: boolean; staticTitle?: boolean; enableMenu?: boolean; @@ -54,6 +55,7 @@ export interface State { firstLaunch?: boolean; steamOSLayoutVersion?: number; + linuxAutoStartEnabled?: boolean; vencordDir?: string; } diff --git a/static/views/about.html b/static/views/about.html index 38ea463..933e31a 100644 --- a/static/views/about.html +++ b/static/views/about.html @@ -28,10 +28,10 @@

Links

@@ -104,4 +103,4 @@ } walk(document.body); - + \ No newline at end of file diff --git a/static/views/updater.html b/static/views/updater.html deleted file mode 100644 index 7eaba43..0000000 --- a/static/views/updater.html +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - -
-
-

Update Available

-

There's a new update for Vesktop! Update now to get new fixes and features!

-

- Current: -
- Latest: -

- -

Changelog

-

Loading...

-
- -
- - -
- - -
-
-
- - - - -