Updates and Linting (#578)
* Updates and Linting * fix lint task * [ImgBot] Optimize images *Total -- 404.98kb -> 304.38kb (24.84%) /assets/screenshot-1920x1080.png -- 205.00kb -> 123.21kb (39.9%) /build/appx/Square150x150Logo.png -- 9.63kb -> 7.71kb (20%) /assets/ac_icon.png -- 40.15kb -> 34.98kb (12.88%) /assets/StoreLogo.png -- 40.15kb -> 34.98kb (12.88%) /assets/Square150x150Logo.png -- 7.24kb -> 6.53kb (9.83%) /assets/ac_icon_transparent.png -- 45.54kb -> 42.00kb (7.76%) /assets/ac_plug_colored.png -- 17.98kb -> 16.72kb (7%) /assets/ac_black_plug.png -- 8.49kb -> 8.06kb (5.06%) /assets/ac_black_plug_hollow.png -- 10.30kb -> 9.95kb (3.4%) /build/appx/Square44x44Logo.png -- 1.69kb -> 1.64kb (2.89%) /assets/Square44x44Logo.png -- 1.90kb -> 1.85kb (2.83%) /assets/Wide310x150Logo.png -- 4.21kb -> 4.17kb (0.97%) /build/appx/Wide310x150Logo.png -- 4.21kb -> 4.17kb (0.97%) /assets/ac_white_plug.png -- 8.49kb -> 8.42kb (0.83%) Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com> * Asyncification!!! Check `REVIEW` comments * More async for `src/discord` * update packages to latest minor version * Void some promises * Add some types - 93 problems left! * make DeepScan Happy * DeepScan part 2 * I am the Constant * preload must be `.mts` * Migrate electron context menu official package * fix bad preload on setup window * fix minor import oversights * fix modloader * Stop main window from continuing during setup * update packages, slow dependabot * Remove paste override, it seems to work without now * IPC typing * Package updates & a few more typings * fix linting errors in screenshare * use pnpm in actions * fix dev releaser? * update node build, fix dev one more time * release action is broke * Fix Release * update actions * actions are so finicky * remove delete-tag-and-release * add github token env * Hopefully this fixes the release workflow * [debug] * this should actually fix it * Fix typo in dev action * put everything in a dir and then get it * use a different releaser * correct release file location * action places it in a folder named x.zip, recurse into that and grab the actual files * Cleanup actions a bit * release is dependent on mac build * remove mac build * split linux arm and x86 * rely on linux arm * remove deprecated action * attempt to fix weird recursive zip * fix env * use pnpm in actions fix dev releaser? update node build, fix dev one more time release action is broke Fix Release update actions actions are so finicky remove delete-tag-and-release add github token env Hopefully this fixes the release workflow [debug] this should actually fix it Fix typo in dev action put everything in a dir and then get it use a different releaser correct release file location action places it in a folder named x.zip, recurse into that and grab the actual files Cleanup actions a bit release is dependent on mac build remove mac build split linux arm and x86 rely on linux arm remove deprecated action attempt to fix weird recursive zip fix env * don't globally install pnpm packages (I don't think the cache checks global) * Type the armcord window * Finalize typings * fix deepscan issues * fix screenshare preload * fix app quitting --------- Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com> Co-authored-by: ImgBotApp <ImgBotHelp@gmail.com> Co-authored-by: smartfrigde <37928912+smartfrigde@users.noreply.github.com>
|
@ -1,35 +0,0 @@
|
||||||
extends: eslint-config-dmitmel/presets/node
|
|
||||||
env:
|
|
||||||
browser: true
|
|
||||||
plugins: ["prettier"]
|
|
||||||
|
|
||||||
settings:
|
|
||||||
node:
|
|
||||||
tryExtensions: [".tsx", ".ts", ".jsx", ".js", ".json", ".node"]
|
|
||||||
|
|
||||||
ignorePatterns: ["src/arrpc/**"]
|
|
||||||
|
|
||||||
rules:
|
|
||||||
prettier/prettier:
|
|
||||||
- error
|
|
||||||
node/no-unsupported-features/es-syntax:
|
|
||||||
- error
|
|
||||||
- ignores:
|
|
||||||
- modules
|
|
||||||
|
|
||||||
overrides:
|
|
||||||
- files: "**/*.ts*"
|
|
||||||
extends:
|
|
||||||
- eslint-config-dmitmel/presets/typescript-addon
|
|
||||||
parserOptions:
|
|
||||||
project: "tsconfig.json"
|
|
||||||
sourceType: module
|
|
||||||
rules:
|
|
||||||
eqeqeq: 0
|
|
||||||
require-await: 0
|
|
||||||
no-undefined: 0
|
|
||||||
node/no-unsupported-features/es-syntax: 0
|
|
||||||
"@typescript-eslint/no-dynamic-delete": 0
|
|
||||||
"@typescript-eslint/no-explicit-any": 0
|
|
||||||
"@typescript-eslint/no-non-null-asserted-optional-chain": 0
|
|
||||||
"@typescript-eslint/naming-convention": 0
|
|
2
.github/dependabot.yml
vendored
|
@ -3,7 +3,7 @@ updates:
|
||||||
- package-ecosystem: "github-actions"
|
- package-ecosystem: "github-actions"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "weekly"
|
||||||
- package-ecosystem: npm
|
- package-ecosystem: npm
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
|
|
3
.github/release.md
vendored
|
@ -1,3 +1,4 @@
|
||||||
# Thanks for checking out ArmCord dev builds!
|
# Thanks for checking out ArmCord dev builds!
|
||||||
These builds are unstable and not ready for full release. They contain new experimental features and changes. We provide no official support for them.
|
|
||||||
|
These builds are unstable and not ready for full release. They contain new experimental features and changes. We provide no official support for them.
|
||||||
Make sure to join our [Discord server](https://discord.gg/uaW5vMY3V6) to share opinions, or to chat with ArmCord developers!
|
Make sure to join our [Discord server](https://discord.gg/uaW5vMY3V6) to share opinions, or to chat with ArmCord developers!
|
||||||
|
|
262
.github/workflows/dev.yml
vendored
|
@ -1,185 +1,147 @@
|
||||||
name: Dev build
|
name: Dev build
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- dev
|
- dev
|
||||||
|
|
||||||
env:
|
env:
|
||||||
FORCE_COLOR: true
|
FORCE_COLOR: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-linux:
|
build-linux:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
|
- uses: pnpm/action-setup@v4
|
||||||
|
|
||||||
- name: Use Node.js 18
|
- name: Use Node.js 22
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 22
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|
||||||
- name: Install Node dependencies
|
- name: Install Node dependencies
|
||||||
run: pnpm install -g cargo-cp-artifact && pnpm install
|
run: pnpm i
|
||||||
|
|
||||||
- name: Install Electron-Builder
|
- name: Build
|
||||||
run: pnpm install -g electron-builder
|
run: pnpm run build && pnpm electron-builder --linux zip
|
||||||
|
|
||||||
- name: Build
|
- name: Upload artifact
|
||||||
run: npm run build && electron-builder --linux zip && electron-builder --arm64 --linux zip
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ArmCordLinux
|
||||||
|
path: dist/armcord-*.zip
|
||||||
|
|
||||||
- name: Upload artifact
|
build-linux-arm:
|
||||||
uses: actions/upload-artifact@v2
|
runs-on: ubuntu-latest
|
||||||
with:
|
steps:
|
||||||
name: ArmCordLinux.zip
|
- name: Checkout code
|
||||||
path: dist/ArmCord-3.3.0.zip
|
uses: actions/checkout@v4
|
||||||
- name: Upload artifact
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: ArmCordLinuxArm64.zip
|
|
||||||
path: dist/ArmCord-3.3.0-arm64.zip
|
|
||||||
build-snap:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
|
- uses: pnpm/action-setup@v4
|
||||||
|
|
||||||
- name: Use Node.js 18
|
- name: Use Node.js 22
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 22
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|
||||||
- name: Install Node dependencies
|
- name: Install Node dependencies
|
||||||
run: pnpm install -g cargo-cp-artifact && pnpm install
|
run: pnpm i
|
||||||
|
|
||||||
- name: Install Electron-Builder
|
- name: Build
|
||||||
run: pnpm install -g electron-builder
|
run: pnpm run build && pnpm electron-builder --arm64 --linux zip
|
||||||
|
|
||||||
- name: Build
|
- name: Upload artifact
|
||||||
run: npm run build && electron-builder --linux snap --config.snap.grade=devel
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ArmCordLinuxArm64
|
||||||
|
path: dist/armcord-*-arm64.zip
|
||||||
|
|
||||||
- uses: snapcore/action-publish@v1
|
build-windows:
|
||||||
env:
|
runs-on: windows-latest
|
||||||
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAP_STORE_LOGIN }}
|
steps:
|
||||||
with:
|
- name: Checkout code
|
||||||
snap: dist/ArmCord_3.3.0_amd64.snap
|
uses: actions/checkout@v4
|
||||||
release: edge
|
|
||||||
build-windows:
|
|
||||||
runs-on: windows-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
|
- uses: pnpm/action-setup@v4
|
||||||
|
|
||||||
- name: Use Node.js 18
|
- name: Use Node.js 22
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 22
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|
||||||
- name: Install Node dependencies
|
- name: Install Node dependencies
|
||||||
run: pnpm install -g cargo-cp-artifact && pnpm install
|
run: pnpm i
|
||||||
|
|
||||||
- name: Install Electron-Builder
|
- name: Build
|
||||||
run: pnpm install -g electron-builder
|
run: pnpm run build && pnpm electron-builder --windows zip
|
||||||
|
|
||||||
- name: Build
|
- name: Upload artifact
|
||||||
run: npm run build && electron-builder --windows zip
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ArmCordWindows
|
||||||
|
path: dist/armcord-3.3.0-win.zip
|
||||||
|
|
||||||
- name: Upload artifact
|
build-windows-arm:
|
||||||
uses: actions/upload-artifact@v2
|
runs-on: windows-latest
|
||||||
with:
|
steps:
|
||||||
name: ArmCordWindows.zip
|
- name: Checkout code
|
||||||
path: dist/ArmCord-3.3.0-win.zip
|
uses: actions/checkout@v4
|
||||||
build-windowsOnARM:
|
|
||||||
runs-on: windows-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: '18'
|
|
||||||
|
|
||||||
- name: Checkout code
|
- uses: actions/setup-node@v4
|
||||||
uses: actions/checkout@v2
|
with:
|
||||||
|
node-version: "22"
|
||||||
|
|
||||||
- name: Set architecture
|
- name: Set architecture
|
||||||
run: set npm_config_arch=arm64
|
run: set npm_config_arch=arm64
|
||||||
|
|
||||||
- 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: Install Node dependencies
|
- name: Install Node dependencies
|
||||||
run: pnpm install -g cargo-cp-artifact && pnpm install
|
run: pnpm i
|
||||||
|
|
||||||
- name: Install Electron-Builder
|
- name: Build
|
||||||
run: pnpm install -g electron-builder
|
run: pnpm run build && pnpm electron-builder --windows zip --arm64
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build
|
- name: Upload artifact
|
||||||
run: npm run build && electron-builder --windows zip --arm64
|
uses: actions/upload-artifact@v4
|
||||||
env:
|
with:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
name: ArmCordWindowsArm64
|
||||||
- name: Upload artifact
|
path: dist\armcord-3.3.0-arm64-win.zip
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: ArmCordWindowsArm64.zip
|
|
||||||
path: dist\ArmCord-3.3.0-arm64-win.zip
|
|
||||||
|
|
||||||
release:
|
release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [build-linux, build-windows, build-windowsOnARM]
|
needs: [build-linux, build-windows, build-windows-arm, build-linux-arm]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: actions/download-artifact@v2
|
- uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ArmCordWindows.zip
|
path: release-files
|
||||||
path: windows
|
|
||||||
|
|
||||||
- uses: actions/download-artifact@v2
|
- name: Get short commit hash
|
||||||
with:
|
id: vars
|
||||||
name: ArmCordLinux.zip
|
run: echo "sha_short=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_OUTPUT
|
||||||
path: linux
|
|
||||||
|
|
||||||
- uses: actions/download-artifact@v2
|
- run: gh release delete devbuild -y --cleanup-tag
|
||||||
with:
|
env:
|
||||||
name: ArmCordLinuxArm64.zip
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
path: linux
|
|
||||||
- uses: actions/download-artifact@v2
|
|
||||||
with:
|
|
||||||
name: ArmCordWindowsArm64.zip
|
|
||||||
path: windows
|
|
||||||
|
|
||||||
- name: Get some values needed for the release
|
- name: Create Release
|
||||||
id: vars
|
uses: ncipollo/release-action@v1
|
||||||
shell: bash
|
env:
|
||||||
run: |
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
|
with:
|
||||||
|
bodyFile: .github/release.md
|
||||||
- uses: dev-drprasad/delete-tag-and-release@v0.2.1
|
generateReleaseNotes: true
|
||||||
with:
|
name: Dev Build ${{ steps.vars.outputs.sha_short }}
|
||||||
delete_release: true
|
prerelease: true
|
||||||
tag_name: devbuild
|
tag: devbuild
|
||||||
env:
|
artifacts: "release-files/**/*.zip"
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Create the release
|
|
||||||
uses: softprops/action-gh-release@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
tag_name: devbuild
|
|
||||||
name: Dev Build ${{ steps.vars.outputs.sha_short }}
|
|
||||||
draft: false
|
|
||||||
prerelease: true
|
|
||||||
body_path: .github/release.md
|
|
||||||
files: |
|
|
||||||
linux/ArmCord-3.3.0.zip
|
|
||||||
linux/ArmCord-3.3.0-arm64.zip
|
|
||||||
windows/ArmCord-3.3.0-win.zip
|
|
||||||
windows/ArmCord-3.3.0-arm64-win.zip
|
|
||||||
|
|
32
.github/workflows/eslint.yml
vendored
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
name: Lint
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "*"
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- "*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
run-linters:
|
||||||
|
name: Run linters
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check out Git repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
cache: pnpm
|
||||||
|
|
||||||
|
- name: Install Node.js dependencies
|
||||||
|
run: pnpm install
|
||||||
|
|
||||||
|
- name: Run linters
|
||||||
|
run: pnpm run lint
|
229
.github/workflows/stable.yml
vendored
|
@ -1,11 +1,11 @@
|
||||||
name: Release build
|
name: Release build
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- stable
|
- stable
|
||||||
|
|
||||||
env:
|
env:
|
||||||
FORCE_COLOR: true
|
FORCE_COLOR: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-linux:
|
build-linux:
|
||||||
|
@ -13,69 +13,67 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
|
- uses: pnpm/action-setup@v4
|
||||||
|
|
||||||
- name: Use Node.js 18
|
- name: Use Node.js 22
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 22
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|
||||||
- name: Install Node dependencies
|
- name: Install Node dependencies
|
||||||
run: pnpm install -g cargo-cp-artifact && pnpm install
|
run: pnpm i -g cargo-cp-artifact electron-builder && pnpm i
|
||||||
|
|
||||||
- name: Install Electron-Builder
|
|
||||||
run: pnpm install -g electron-builder
|
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: npm run build && electron-builder --linux && electron-builder --arm64 --linux && electron-builder --armv7l --linux
|
run: pnpm run build && electron-builder --linux && electron-builder --arm64 --linux && electron-builder --armv7l --linux
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: List all files in the dist directory
|
- name: List all files in the dist directory
|
||||||
run: ls -l dist
|
run: ls -l dist
|
||||||
|
|
||||||
- name: Delete unpacked builds
|
- name: Delete unpacked builds
|
||||||
run: rm -rf dist/linux-unpacked && rm -rf dist/linux-arm64-unpacked && rm -rf dist/linux-armv7l-unpacked
|
run: rm -rf dist/linux-unpacked && rm -rf dist/linux-arm64-unpacked && rm -rf dist/linux-armv7l-unpacked
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ArmCordLinux
|
name: ArmCordLinux
|
||||||
path: dist/
|
path: dist/
|
||||||
|
|
||||||
|
|
||||||
build-mac:
|
build-mac:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
|
- uses: pnpm/action-setup@v4
|
||||||
|
|
||||||
- name: Use Node.js 18
|
- name: Use Node.js 22
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 22
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|
||||||
- name: Install Node dependencies
|
- name: Install Node dependencies
|
||||||
run: pnpm install -g cargo-cp-artifact && pnpm install
|
run: pnpm i -g cargo-cp-artifact electron-builder && pnpm i
|
||||||
|
|
||||||
- name: Install Electron-Builder
|
|
||||||
run: pnpm install -g electron-builder
|
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: npm run build && electron-builder --macos --x64 --arm64
|
run: pnpm run build && electron-builder --macos --x64 --arm64
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: List all files in the dist directory
|
- name: List all files in the dist directory
|
||||||
run: ls -l dist
|
run: ls -l dist
|
||||||
|
|
||||||
- name: Delete unpacked builds
|
- name: Delete unpacked builds
|
||||||
run: rm -rf dist/macos-unpacked
|
run: rm -rf dist/macos-unpacked
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ArmCordMac
|
name: ArmCordMac
|
||||||
path: dist/
|
path: dist/
|
||||||
|
@ -84,62 +82,62 @@ jobs:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
|
- uses: pnpm/action-setup@v4
|
||||||
|
|
||||||
- name: Use Node.js 18
|
- name: Use Node.js 22
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 22
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|
||||||
- name: Install Node dependencies
|
- name: Install Node dependencies
|
||||||
run: pnpm install -g cargo-cp-artifact && pnpm install
|
run: pnpm i -g cargo-cp-artifact electron-builder && pnpm i
|
||||||
|
|
||||||
- name: Install Electron-Builder
|
|
||||||
run: pnpm install -g electron-builder
|
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: npm run build && electron-builder --windows
|
run: pnpm run build && electron-builder --windows
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Delete unpacked builds
|
- name: Delete unpacked builds
|
||||||
run: Remove-Item -LiteralPath ".\dist\win-unpacked" -Force -Recurse
|
run: Remove-Item -LiteralPath ".\dist\win-unpacked" -Force -Recurse
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ArmCordWindows
|
name: ArmCordWindows
|
||||||
path: dist/
|
path: dist/
|
||||||
|
|
||||||
build-windowsOnARM:
|
build-windowsOnARM:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: '18'
|
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "22"
|
||||||
|
cache: "pnpm"
|
||||||
|
|
||||||
- name: Set architecture
|
- name: Set architecture
|
||||||
run: set npm_config_arch=arm64
|
run: set npm_config_arch=arm64
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
|
|
||||||
|
|
||||||
- name: Install Node dependencies
|
- name: Install Node dependencies
|
||||||
run: pnpm install -g cargo-cp-artifact && pnpm install
|
run: pnpm install -g cargo-cp-artifact electron-builder && pnpm install
|
||||||
|
|
||||||
- name: Install Electron-Builder
|
|
||||||
run: pnpm install -g electron-builder
|
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: npm run build && electron-builder --windows --arm64
|
run: pnpm run build && electron-builder --windows --arm64
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Delete unpacked builds
|
- name: Delete unpacked builds
|
||||||
run: Remove-Item -LiteralPath ".\dist\win-arm64-unpacked" -Force -Recurse
|
run: Remove-Item -LiteralPath ".\dist\win-arm64-unpacked" -Force -Recurse
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ArmCordWindowsArm64
|
name: ArmCordWindowsArm64
|
||||||
path: dist/
|
path: dist/
|
||||||
|
@ -148,86 +146,93 @@ jobs:
|
||||||
needs: [build-linux, build-mac, build-windows, build-windowsOnARM]
|
needs: [build-linux, build-mac, build-windows, build-windowsOnARM]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@v2
|
- uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ArmCordMac
|
name: ArmCordMac
|
||||||
path: macos
|
path: macos
|
||||||
- uses: actions/download-artifact@v2
|
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ArmCordWindows
|
name: ArmCordWindows
|
||||||
path: windows
|
path: windows
|
||||||
- uses: actions/download-artifact@v2
|
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ArmCordWindowsArm64
|
name: ArmCordWindowsArm64
|
||||||
path: windows
|
path: windows
|
||||||
- uses: actions/download-artifact@v2
|
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ArmCordLinux
|
name: ArmCordLinux
|
||||||
path: linux
|
path: linux
|
||||||
|
|
||||||
- name: ls
|
- name: ls
|
||||||
run: ls
|
run: ls
|
||||||
|
|
||||||
- name: Delete unwanted directories
|
- name: Delete unwanted directories
|
||||||
run: rm -rf {linux,macos,windows}/*/
|
run: rm -rf {linux,macos,windows}/*/
|
||||||
rm -rf {linux,macos,windows}/.icon*
|
rm -rf {linux,macos,windows}/.icon*
|
||||||
rm -rf {linux,macos,windows}/builder-debug.yml
|
rm -rf {linux,macos,windows}/builder-debug.yml
|
||||||
|
|
||||||
- name: ls dirs
|
- name: ls dirs
|
||||||
run: ls linux && ls macos && ls windows
|
run: ls linux && ls macos && ls windows
|
||||||
|
|
||||||
- name: Get some values needed for the release
|
- name: Get some values needed for the release
|
||||||
id: vars
|
id: vars
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=releaseTag::$(git describe --tags --abbrev=0)"
|
echo "::set-output name=releaseTag::$(git describe --tags --abbrev=0)"
|
||||||
|
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
uses: actions/github-script@v2
|
uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||||
script: |
|
script: |
|
||||||
console.log('environment', process.versions);
|
console.log('environment', process.versions);
|
||||||
|
|
||||||
const fs = require('fs').promises;
|
|
||||||
|
|
||||||
const { repo: { owner, repo }, sha } = context;
|
|
||||||
console.log({ owner, repo, sha });
|
|
||||||
|
|
||||||
const release = await github.repos.createRelease({
|
const fs = require('fs').promises;
|
||||||
owner, repo,
|
|
||||||
tag_name: process.env.releaseTag,
|
|
||||||
draft: true,
|
|
||||||
target_commitish: sha
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('created release', { release });
|
const { repo: { owner, repo }, sha } = context;
|
||||||
|
console.log({ owner, repo, sha });
|
||||||
for (let file of await fs.readdir('linux')) {
|
|
||||||
// do whatever filtering you want here, I'm just uploading all the files
|
const release = await github.repos.createRelease({
|
||||||
console.log('uploading', file);
|
owner, repo,
|
||||||
await github.repos.uploadReleaseAsset({
|
tag_name: process.env.releaseTag,
|
||||||
owner, repo,
|
draft: true,
|
||||||
release_id: release.data.id,
|
target_commitish: sha
|
||||||
name: file,
|
});
|
||||||
data: await fs.readFile(`./linux/${file}`)
|
|
||||||
});
|
console.log('created release', { release });
|
||||||
}
|
|
||||||
for (let file of await fs.readdir('windows')) {
|
for (let file of await fs.readdir('linux')) {
|
||||||
// do whatever filtering you want here, I'm just uploading all the files
|
// do whatever filtering you want here, I'm just uploading all the files
|
||||||
console.log('uploading', file);
|
console.log('uploading', file);
|
||||||
await github.repos.uploadReleaseAsset({
|
await github.repos.uploadReleaseAsset({
|
||||||
owner, repo,
|
owner, repo,
|
||||||
release_id: release.data.id,
|
release_id: release.data.id,
|
||||||
name: file,
|
name: file,
|
||||||
data: await fs.readFile(`./windows/${file}`)
|
data: await fs.readFile(`./linux/${file}`)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
for (let file of await fs.readdir('macos')) {
|
for (let file of await fs.readdir('windows')) {
|
||||||
// do whatever filtering you want here, I'm just uploading all the files
|
// do whatever filtering you want here, I'm just uploading all the files
|
||||||
console.log('uploading', file);
|
console.log('uploading', file);
|
||||||
await github.repos.uploadReleaseAsset({
|
await github.repos.uploadReleaseAsset({
|
||||||
owner, repo,
|
owner, repo,
|
||||||
release_id: release.data.id,
|
release_id: release.data.id,
|
||||||
name: file,
|
name: file,
|
||||||
data: await fs.readFile(`./macos/${file}`)
|
data: await fs.readFile(`./windows/${file}`)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
for (let file of await fs.readdir('macos')) {
|
||||||
|
// do whatever filtering you want here, I'm just uploading all the files
|
||||||
|
console.log('uploading', file);
|
||||||
|
await github.repos.uploadReleaseAsset({
|
||||||
|
owner, repo,
|
||||||
|
release_id: release.data.id,
|
||||||
|
name: file,
|
||||||
|
data: await fs.readFile(`./macos/${file}`)
|
||||||
|
});
|
||||||
|
}
|
||||||
env:
|
env:
|
||||||
releaseTag: ${{ steps.vars.outputs.releaseTag }}
|
releaseTag: ${{ steps.vars.outputs.releaseTag }}
|
||||||
|
|
18
.github/workflows/winget.yml
vendored
|
@ -1,13 +1,13 @@
|
||||||
name: Publish to WinGet
|
name: Publish to WinGet
|
||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
types: [released]
|
types: [released]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
publish:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: vedantmgoyal2009/winget-releaser@v2
|
- uses: vedantmgoyal2009/winget-releaser@v2
|
||||||
with:
|
with:
|
||||||
identifier: ArmCord.ArmCord
|
identifier: ArmCord.ArmCord
|
||||||
token: ${{ secrets.WINGET_TOKEN }}
|
token: ${{ secrets.WINGET_TOKEN }}
|
||||||
|
|
1
.gitignore
vendored
|
@ -4,3 +4,4 @@ dist
|
||||||
ts-out/
|
ts-out/
|
||||||
ts-out
|
ts-out
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
.pnpm-store
|
|
@ -1,5 +1,5 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
npm run format
|
pnpm run format
|
||||||
git add -A
|
git add -A
|
||||||
|
|
5
.vscode/extensions.json
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"ExodiusStudios.comment-anchors"
|
||||||
|
]
|
||||||
|
}
|
19
.vscode/settings.json
vendored
|
@ -1,2 +1,21 @@
|
||||||
{
|
{
|
||||||
|
"cSpell.words": [
|
||||||
|
"armcord",
|
||||||
|
"armcordinternal",
|
||||||
|
"arrpc",
|
||||||
|
"Autogain",
|
||||||
|
"clientmod",
|
||||||
|
"copyfiles",
|
||||||
|
"Ducko",
|
||||||
|
"modloader",
|
||||||
|
"nsis",
|
||||||
|
"smartfridge",
|
||||||
|
"smartfrigde",
|
||||||
|
"togglefullscreen",
|
||||||
|
"unmaximize",
|
||||||
|
"vaapi"
|
||||||
|
],
|
||||||
|
"cSpell.ignorePaths": [
|
||||||
|
"assets/lang"
|
||||||
|
]
|
||||||
}
|
}
|
81
README.md
|
@ -1,17 +1,17 @@
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="https://armcord.app/logo.png" width="520">
|
<img src="https://armcord.app/logo.png" width="520">
|
||||||
<br>ArmCord is a custom client designed to enhance your Discord experience while keeping everything lightweight.
|
<br>ArmCord is a custom client designed to enhance your Discord experience while keeping everything lightweight.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
- **Standalone client**
|
- **Standalone client**
|
||||||
|
|
||||||
ArmCord is built as a standalone client and doesn't rely on the original Discord client in any way.
|
ArmCord is built as a standalone client and doesn't rely on the original Discord client in any way.
|
||||||
|
|
||||||
- **Various mods built-in**
|
- **Various mods built-in**
|
||||||
|
|
||||||
Enjoy [Vencord](https://github.com/Vendicated/Vencord), [Shelter](https://github.com/uwu/shelter) and their many features, or have a more vanilla experience, it's your choice!
|
Enjoy [Vencord](https://github.com/Vendicated/Vencord), [Shelter](https://github.com/uwu/shelter) and their many features, or have a more vanilla experience, it's your choice!
|
||||||
|
|
||||||
- **Themes**
|
- **Themes**
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
- **Supports Rich Presence**
|
- **Supports Rich Presence**
|
||||||
|
|
||||||
Unlike other clients, ArmCord supports rich presence (game activity) out of the box thanks to [arRPC](https://arrpc.openasar.dev).
|
Unlike other clients, ArmCord supports rich presence (game activity) out of the box thanks to [arRPC](https://arrpc.openasar.dev).
|
||||||
|
|
||||||
- **Mobile support**
|
- **Mobile support**
|
||||||
|
|
||||||
ArmCord has **experimental** mobile support for phones running Linux such as the PinePhone. While this is still far from an ideal solution, we're slowly trying to improve it.
|
ArmCord has **experimental** mobile support for phones running Linux such as the PinePhone. While this is still far from an ideal solution, we're slowly trying to improve it.
|
||||||
|
@ -34,7 +34,6 @@
|
||||||
|
|
||||||
ArmCord is using a newer build of Electron than the stock Discord app. This means you can have a much more stable and secure experience, along with slightly better performance.
|
ArmCord is using a newer build of Electron than the stock Discord app. This means you can have a much more stable and secure experience, along with slightly better performance.
|
||||||
|
|
||||||
|
|
||||||
- **Cross-platform support!**
|
- **Cross-platform support!**
|
||||||
|
|
||||||
ArmCord was originally created for ARM64 Linux devices since Discord doesn't support them. We soon decided to support every platform that [Electron supports](https://github.com/electron/electron#platform-support)!
|
ArmCord was originally created for ARM64 Linux devices since Discord doesn't support them. We soon decided to support every platform that [Electron supports](https://github.com/electron/electron#platform-support)!
|
||||||
|
@ -42,9 +41,11 @@
|
||||||
# How to run/install it?
|
# How to run/install it?
|
||||||
|
|
||||||
## Packaging status
|
## Packaging status
|
||||||
|
|
||||||
[![Packaging status](https://repology.org/badge/vertical-allrepos/armcord.svg)](https://repology.org/project/armcord/versions)
|
[![Packaging status](https://repology.org/badge/vertical-allrepos/armcord.svg)](https://repology.org/project/armcord/versions)
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
<a href="https://microsoft.com/store/apps/9PFHLJFD7KJT">
|
<a href="https://microsoft.com/store/apps/9PFHLJFD7KJT">
|
||||||
<img src="https://get.microsoft.com/images/en-us%20dark.svg" alt="Download ArmCord" />
|
<img src="https://get.microsoft.com/images/en-us%20dark.svg" alt="Download ArmCord" />
|
||||||
</a>
|
</a>
|
||||||
|
@ -52,106 +53,146 @@
|
||||||
If you're using an older version of Windows, you need to use [pre-built installers](https://www.armcord.app/download).
|
If you're using an older version of Windows, you need to use [pre-built installers](https://www.armcord.app/download).
|
||||||
|
|
||||||
### Flatpak
|
### Flatpak
|
||||||
|
|
||||||
<a href='https://flathub.org/apps/details/xyz.armcord.ArmCord'><img width='240' alt='Download on Flathub' src='https://flathub.org/assets/badges/flathub-badge-en.svg'/></a>
|
<a href='https://flathub.org/apps/details/xyz.armcord.ArmCord'><img width='240' alt='Download on Flathub' src='https://flathub.org/assets/badges/flathub-badge-en.svg'/></a>
|
||||||
|
|
||||||
### Debian, Ubuntu and Raspbian repository
|
### Debian, Ubuntu and Raspbian repository
|
||||||
|
|
||||||
ArmCord is available on our official repositories for `apt` package manager. By using this method you'll receive automatic updates and get all the dependencies. Run the following commands to install ArmCord from them:
|
ArmCord is available on our official repositories for `apt` package manager. By using this method you'll receive automatic updates and get all the dependencies. Run the following commands to install ArmCord from them:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
curl -fsSL https://apt.armcord.app/public.gpg | sudo gpg --dearmor -o /usr/share/keyrings/armcord.gpg
|
curl -fsSL https://apt.armcord.app/public.gpg | sudo gpg --dearmor -o /usr/share/keyrings/armcord.gpg
|
||||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/armcord.gpg] https://apt.armcord.app/ stable main" | sudo tee /etc/apt/sources.list.d/armcord.list
|
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/armcord.gpg] https://apt.armcord.app/ stable main" | sudo tee /etc/apt/sources.list.d/armcord.list
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install armcord
|
sudo apt install armcord
|
||||||
```
|
```
|
||||||
|
|
||||||
If you previously used old ArmCord apt repo, here's how you can remove it:
|
If you previously used old ArmCord apt repo, here's how you can remove it:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo rm /etc/apt/sources.list.d/armcord.list
|
sudo rm /etc/apt/sources.list.d/armcord.list
|
||||||
sudo rm /usr/share/keyrings/armcord.gpg
|
sudo rm /usr/share/keyrings/armcord.gpg
|
||||||
sudo apt update
|
sudo apt update
|
||||||
```
|
```
|
||||||
|
|
||||||
### Snap package
|
### Snap package
|
||||||
|
|
||||||
ArmCord is also available on the Snap store [here](https://snapcraft.io/armcord).
|
ArmCord is also available on the Snap store [here](https://snapcraft.io/armcord).
|
||||||
<a href="https://snapcraft.io/armcord">
|
<a href="https://snapcraft.io/armcord">
|
||||||
<img alt="Get it from the Snap Store" src="https://snapcraft.io/static/images/badges/en/snap-store-black.svg" />
|
<img alt="Get it from the Snap Store" src="https://snapcraft.io/static/images/badges/en/snap-store-black.svg" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
Similar to `armcord-git` on AUR, you can install the latest dev builds through snaps by running this command:
|
Similar to `armcord-git` on AUR, you can install the latest dev builds through snaps by running this command:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo snap install armcord --channel=latest/edge
|
sudo snap install armcord --channel=latest/edge
|
||||||
```
|
```
|
||||||
|
|
||||||
Snapd will automatically update the app including developer builds.
|
Snapd will automatically update the app including developer builds.
|
||||||
|
|
||||||
### Winget Package
|
### Winget Package
|
||||||
|
|
||||||
ArmCord is also available on the [winget-pkgs](https://github.com/microsoft/winget-pkgs) repository:
|
ArmCord is also available on the [winget-pkgs](https://github.com/microsoft/winget-pkgs) repository:
|
||||||
```
|
|
||||||
|
```ps1
|
||||||
winget install ArmCord.ArmCord
|
winget install ArmCord.ArmCord
|
||||||
```
|
```
|
||||||
|
|
||||||
### Scoop package
|
### Scoop package
|
||||||
|
|
||||||
ArmCord is also available on [Scoop extras](https://github.com/ScoopInstaller/Extras) repo:
|
ArmCord is also available on [Scoop extras](https://github.com/ScoopInstaller/Extras) repo:
|
||||||
```
|
|
||||||
|
```ps1
|
||||||
scoop bucket add extras
|
scoop bucket add extras
|
||||||
```
|
```
|
||||||
```
|
|
||||||
|
```ps1
|
||||||
scoop install armcord
|
scoop install armcord
|
||||||
```
|
```
|
||||||
|
|
||||||
### AUR Package
|
### AUR Package
|
||||||
|
|
||||||
ArmCord is also available on the Arch User Repository (AUR):
|
ArmCord is also available on the Arch User Repository (AUR):
|
||||||
|
|
||||||
- [armcord-bin](https://aur.archlinux.org/packages/armcord-bin) - ArmCord Release ~ Static binary from release, stable release only
|
- [armcord-bin](https://aur.archlinux.org/packages/armcord-bin) - ArmCord Release ~ Static binary from release, stable release only
|
||||||
|
|
||||||
- [armcord-git](https://aur.archlinux.org/packages/armcord-git) - ArmCord Dev ~ Latest devbuild built from source (takes ~1 minute) using the system electron
|
- [armcord-git](https://aur.archlinux.org/packages/armcord-git) - ArmCord Dev ~ Latest devbuild built from source (takes ~1 minute) using the system electron
|
||||||
|
|
||||||
Install it via an AUR helper tool like `yay`.
|
Install it via an AUR helper tool like `yay`.
|
||||||
|
|
||||||
**Example:** `yay -S armcord-bin`
|
**Example:** `yay -S armcord-bin`
|
||||||
|
|
||||||
### Homebrew repository
|
### Homebrew repository
|
||||||
ArmcCord also has a homebrew repository
|
|
||||||
```
|
ArmCord also has a homebrew repository
|
||||||
|
|
||||||
|
```zsh
|
||||||
brew tap armcord/armcord
|
brew tap armcord/armcord
|
||||||
```
|
```
|
||||||
```
|
|
||||||
|
```zsh
|
||||||
brew install --cask armcord
|
brew install --cask armcord
|
||||||
```
|
```
|
||||||
|
|
||||||
### FreeBSD
|
### FreeBSD
|
||||||
|
|
||||||
You can also get ArmCord running on FreeBSD by following [these instructions](https://gist.github.com/axyiee/4d29c982ac85d5d26f98a51040b5de37).
|
You can also get ArmCord running on FreeBSD by following [these instructions](https://gist.github.com/axyiee/4d29c982ac85d5d26f98a51040b5de37).
|
||||||
|
|
||||||
### Pi-Apps
|
### Pi-Apps
|
||||||
|
|
||||||
ArmCord is also available in [Pi-Apps](https://github.com/Botspot/pi-apps).
|
ArmCord is also available in [Pi-Apps](https://github.com/Botspot/pi-apps).
|
||||||
[![badge](https://github.com/Botspot/pi-apps/blob/master/icons/badge.png?raw=true)](https://github.com/Botspot/pi-apps)
|
[![badge](https://github.com/Botspot/pi-apps/blob/master/icons/badge.png?raw=true)](https://github.com/Botspot/pi-apps)
|
||||||
|
|
||||||
### Pre-built binaries:
|
### Pre-built binaries:
|
||||||
|
|
||||||
Check the **releases tab** for precompiled packages for Linux, Windows, and Mac OS. Alternatively, use our Sourceforge mirror.
|
Check the **releases tab** for precompiled packages for Linux, Windows, and Mac OS. Alternatively, use our Sourceforge mirror.
|
||||||
<a href="https://sourceforge.net/projects/armcord/files/latest/download"><img alt="Download ArmCord" src="https://a.fsdn.com/con/app/sf-download-button" width=276 height=48 srcset="https://a.fsdn.com/con/app/sf-download-button?button_size=2x 2x"></a>
|
<a href="https://sourceforge.net/projects/armcord/files/latest/download"><img alt="Download ArmCord" src="https://a.fsdn.com/con/app/sf-download-button" width=276 height=48 srcset="https://a.fsdn.com/con/app/sf-download-button?button_size=2x 2x"></a>
|
||||||
|
|
||||||
### Compiling:
|
### Compiling:
|
||||||
Alternatively, you can run ArmCord from source ([NodeJS](https://nodejs.dev), [pnpm](https://pnpm.io/installation#using-npm), and [rust toolchain](https://www.rust-lang.org/tools/install) are required):
|
|
||||||
1. Clone ArmCord repo: `git clone https://github.com/ArmCord/ArmCord.git`
|
|
||||||
2. Run `pnpm install` to install dependencies
|
|
||||||
3. Build with `npm run build`
|
|
||||||
4. Compile/Package with `npm run package`
|
|
||||||
|
|
||||||
|
Alternatively, you can run ArmCord from source ([NodeJS](https://nodejs.dev), [pnpm](https://pnpm.io/installation#using-npm), and [rust toolchain](https://www.rust-lang.org/tools/install) are required):
|
||||||
|
|
||||||
|
1. Clone ArmCord repo: `git clone https://github.com/ArmCord/ArmCord.git`
|
||||||
|
2. Run `pnpm install` to install dependencies
|
||||||
|
3. Build with `pnpm run build`
|
||||||
|
4. Compile/Package with `pnpm run package`
|
||||||
|
|
||||||
# FAQ
|
# FAQ
|
||||||
|
|
||||||
## Do you have a support Discord?
|
## Do you have a support Discord?
|
||||||
|
|
||||||
[![](https://dcbadge.vercel.app/api/server/TnhxcqynZ2)](https://discord.gg/TnhxcqynZ2)
|
[![](https://dcbadge.vercel.app/api/server/TnhxcqynZ2)](https://discord.gg/TnhxcqynZ2)
|
||||||
## Will I get banned for using this?
|
|
||||||
|
## Will I get banned for using this?
|
||||||
|
|
||||||
- You are breaking [Discord ToS](https://discord.com/terms#software-in-discord%E2%80%99s-services) by using ArmCord, but no one has been banned from using it or any of the client mods included.
|
- You are breaking [Discord ToS](https://discord.com/terms#software-in-discord%E2%80%99s-services) by using ArmCord, but no one has been banned from using it or any of the client mods included.
|
||||||
|
|
||||||
## Can I use this on anything other than ARM?
|
## Can I use this on anything other than ARM?
|
||||||
|
|
||||||
- Yes! ArmCord should work normally under Windows, MacOS, and Linux as long as it has Electron support.
|
- Yes! ArmCord should work normally under Windows, MacOS, and Linux as long as it has Electron support.
|
||||||
|
|
||||||
## How can I access the settings?
|
## How can I access the settings?
|
||||||
|
|
||||||
- Open Discord settings and there should be a button `ArmCord Settings` button with a white Discord icon, you can also right click on the tray icon and click `Open Settings`
|
- Open Discord settings and there should be a button `ArmCord Settings` button with a white Discord icon, you can also right click on the tray icon and click `Open Settings`
|
||||||
|
|
||||||
## How does this work?
|
## How does this work?
|
||||||
|
|
||||||
- We are using the official web app and wrapping it up in Electron. While you may think this is lame and done like thousands of times before, what makes us unique is that we actually strive for creating a customized experience. You can very easily load themes and mods with no installers/injectors. You can even make the client have transparency effects and follow the fluent design of Windows! At its core, it's just a simple web wrapper, however, we applied many patches to make this work well for you <3
|
- We are using the official web app and wrapping it up in Electron. While you may think this is lame and done like thousands of times before, what makes us unique is that we actually strive for creating a customized experience. You can very easily load themes and mods with no installers/injectors. You can even make the client have transparency effects and follow the fluent design of Windows! At its core, it's just a simple web wrapper, however, we applied many patches to make this work well for you <3
|
||||||
|
|
||||||
## Why is macOS support lacking?
|
## Why is MacOS support lacking?
|
||||||
|
|
||||||
- Due to me not owning any macOS device, I can't easily debug/test or do anything related to it. Of course, VMs and Hackintosh machines exist but from my experience, these are unreliable or very time-consuming to set up and maintain. While ArmCord "works" on macOS you may encounter weird issues or inconsistencies with other apps in terms of how they behave (for example macOS lack of tray).
|
- Due to me not owning any macOS device, I can't easily debug/test or do anything related to it. Of course, VMs and Hackintosh machines exist but from my experience, these are unreliable or very time-consuming to set up and maintain. While ArmCord "works" on macOS you may encounter weird issues or inconsistencies with other apps in terms of how they behave (for example macOS lack of tray).
|
||||||
|
|
||||||
## Where can I find the source code?
|
## Where can I find the source code?
|
||||||
|
|
||||||
- The source code is on [GitHub](https://github.com/ArmCord/ArmCord/).
|
- The source code is on [GitHub](https://github.com/ArmCord/ArmCord/).
|
||||||
|
|
||||||
## Where can I translate this?
|
## Where can I translate this?
|
||||||
|
|
||||||
- Translations are done using our [Weblate page](https://hosted.weblate.org/projects/armcord/armcord/).
|
- Translations are done using our [Weblate page](https://hosted.weblate.org/projects/armcord/armcord/).
|
||||||
|
|
||||||
# Credits
|
# Credits
|
||||||
|
|
||||||
- [ArmCord UI design, branding, and a few features](https://github.com/kckarnige)
|
- [ArmCord UI design, branding, and a few features](https://github.com/kckarnige)
|
||||||
- [OpenAsar](https://github.com/GooseMod/OpenAsar)
|
- [OpenAsar](https://github.com/GooseMod/OpenAsar)
|
||||||
- [arRPC (for Rich Presence)](https://github.com/OpenAsar/arrpc)
|
- [arRPC (for Rich Presence)](https://github.com/OpenAsar/arrpc)
|
||||||
|
@ -162,4 +203,4 @@ ArmCord is also available in [Pi-Apps](https://github.com/Botspot/pi-apps).
|
||||||
- (Pre v3.0.0) [custom-electron-titlebar](https://github.com/AlexTorresSk/custom-electron-titlebar)
|
- (Pre v3.0.0) [custom-electron-titlebar](https://github.com/AlexTorresSk/custom-electron-titlebar)
|
||||||
- [electron-builder](https://electron.build)
|
- [electron-builder](https://electron.build)
|
||||||
|
|
||||||
Discord is trademark of Discord Inc. ArmCord is not affiliated with or endorsed by Discord Inc.
|
Discord is trademark of Discord Inc. ArmCord is not affiliated with or endorsed by Discord Inc.
|
||||||
|
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 205 KiB After Width: | Height: | Size: 123 KiB |
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
48
eslint.config.js
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
/* eslint-disable n/no-unpublished-import */
|
||||||
|
// @ts-check
|
||||||
|
|
||||||
|
import eslint from "@eslint/js";
|
||||||
|
import tseslint from "typescript-eslint";
|
||||||
|
import prettier from "eslint-plugin-prettier";
|
||||||
|
import n from "eslint-plugin-n";
|
||||||
|
|
||||||
|
export default tseslint.config(
|
||||||
|
eslint.configs.recommended,
|
||||||
|
{ignores: ["ts-out", "src/discord/content/js"]}, // REVIEW - investigate discord files a bit before finalizing this - I think these are meant to be run in the app console, and this would be difficult to type
|
||||||
|
...tseslint.configs.recommendedTypeChecked,
|
||||||
|
...tseslint.configs.stylisticTypeChecked,
|
||||||
|
n.configs["flat/recommended"],
|
||||||
|
{
|
||||||
|
settings: {
|
||||||
|
n: {
|
||||||
|
allowModules: ["electron"],
|
||||||
|
tryExtensions: [".tsx", ".ts", ".jsx", ".js", ".json", ".node", ".d.ts"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
prettier,
|
||||||
|
n
|
||||||
|
},
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
project: true,
|
||||||
|
tsconfigRootDir: import.meta.dirname
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
|
rules: {
|
||||||
|
"no-constant-binary-expression": 0,
|
||||||
|
"n/no-unsupported-features/node-builtins": 1,
|
||||||
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
2,
|
||||||
|
{
|
||||||
|
argsIgnorePattern: "^_",
|
||||||
|
varsIgnorePattern: "^_",
|
||||||
|
caughtErrorsIgnorePattern: "^_"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
// @ts-expect-error - Don't worry about it
|
||||||
|
...prettier.configs.recommended.rules
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
52
package.json
|
@ -1,23 +1,23 @@
|
||||||
{
|
{
|
||||||
"name": "ArmCord",
|
"name": "armcord",
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"description": "ArmCord is a custom client designed to enhance your Discord experience while keeping everything lightweight.",
|
"description": "ArmCord is a custom client designed to enhance your Discord experience while keeping everything lightweight.",
|
||||||
"main": "ts-out/main.js",
|
"main": "ts-out/main.js",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"node": ">=22"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc && copyfiles -u 1 src/**/*.html src/**/**/*.css src/**/**/*.js ts-out/ && copyfiles package.json ts-out/ && copyfiles assets/**/** ts-out/",
|
"build": "tsc && copyfiles -u 1 src/**/*.html src/**/**/*.css src/**/**/*.js ts-out/ && copyfiles package.json ts-out/ && copyfiles assets/**/** ts-out/",
|
||||||
"watch": "tsc -w",
|
"watch": "tsc -w",
|
||||||
"start": "npm run build && electron ./ts-out/main.js",
|
"start": "pnpm run build && electron ./ts-out/main.js",
|
||||||
"startThemeManager": "npm run build && electron ./ts-out/main.js themes",
|
"startThemeManager": "pnpm run build && electron ./ts-out/main.js themes",
|
||||||
"startKeybindManager": "npm run build && electron ./ts-out/main.js keybinds",
|
"startKeybindManager": "pnpm run build && electron ./ts-out/main.js keybinds",
|
||||||
"startWayland": "npm run build && electron ./ts-out/main.js --ozone-platform-hint=auto --enable-features=WebRTCPipeWireCapturer,WaylandWindowDecorations --disable-gpu",
|
"startWayland": "pnpm run build && electron ./ts-out/main.js --ozone-platform-hint=auto --enable-features=WebRTCPipeWireCapturer,WaylandWindowDecorations --disable-gpu",
|
||||||
"package": "npm run build && electron-builder",
|
"package": "pnpm run build && electron-builder",
|
||||||
"packageQuick": "npm run build && electron-builder --dir",
|
"packageQuick": "pnpm run build && electron-builder --dir",
|
||||||
"format": "prettier --write src *.json",
|
"format": "prettier --write src *.json",
|
||||||
"lint": "eslint src --ext .js,.jsx,.ts,.tsx --ignore-path .gitignore",
|
"lint": "eslint \"**/*.{ts,tsx,js,jsx}\" .",
|
||||||
"CIbuild": "npm run build && electron-builder --linux zip && electron-builder --windows zip && electron-builder --macos zip",
|
"CIbuild": "pnpm run build && electron-builder --linux zip && electron-builder --windows zip && electron-builder --macos zip",
|
||||||
"prepare": "git config --local core.hooksPath .hooks/"
|
"prepare": "git config --local core.hooksPath .hooks/"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -32,27 +32,27 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/armcord/armcord#readme",
|
"homepage": "https://github.com/armcord/armcord#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.11.1",
|
"@eslint/js": "^9.4.0",
|
||||||
"@types/ws": "^8.5.3",
|
"@types/eslint__js": "^8.42.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.59.2",
|
"@types/node": "^20.14.2",
|
||||||
"@typescript-eslint/parser": "^5.59.2",
|
"@types/ws": "^8.5.10",
|
||||||
"copyfiles": "^2.4.1",
|
"copyfiles": "^2.4.1",
|
||||||
"electron": "30.0.5",
|
"electron": "30.1.0",
|
||||||
"electron-builder": "^24.13.3",
|
"electron-builder": "25.0.0-alpha.9",
|
||||||
"eslint": "^8.40.0",
|
"eslint": "^9.4.0",
|
||||||
"eslint-config-dmitmel": "github:dmitmel/eslint-config-dmitmel",
|
"eslint-plugin-n": "^17.8.1",
|
||||||
"eslint-plugin-node": "^11.1.0",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"prettier": "^3.3.1",
|
||||||
"prettier": "^2.7.1",
|
"typescript": "^5.4.5",
|
||||||
"typescript": "^4.9.3"
|
"typescript-eslint": "^7.12.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"arrpc": "github:OpenAsar/arrpc",
|
"arrpc": "github:OpenAsar/arrpc",
|
||||||
"cross-fetch": "^3.1.5",
|
"cross-fetch": "^4.0.0",
|
||||||
"electron-context-menu": "^4.0.0",
|
"electron-context-menu": "^4.0.0",
|
||||||
"extract-zip": "^2.0.1",
|
"extract-zip": "^2.0.1",
|
||||||
"v8-compile-cache": "^2.3.0",
|
"v8-compile-cache": "^2.4.0",
|
||||||
"ws": "^8.11.0"
|
"ws": "^8.17.0"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"snap": {
|
"snap": {
|
||||||
|
@ -95,6 +95,6 @@
|
||||||
"applicationId": "smartfrigde.ArmCord"
|
"applicationId": "smartfrigde.ArmCord"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.1.1",
|
"packageManager": "pnpm@9.2.0",
|
||||||
"package-manager-strict": false
|
"package-manager-strict": false
|
||||||
}
|
}
|
||||||
|
|
2218
pnpm-lock.yaml
|
@ -1,4 +1,5 @@
|
||||||
module.exports = {
|
/** @type {import("prettier").Config} */
|
||||||
|
const config = {
|
||||||
printWidth: 120,
|
printWidth: 120,
|
||||||
tabWidth: 4,
|
tabWidth: 4,
|
||||||
useTabs: false,
|
useTabs: false,
|
||||||
|
@ -12,3 +13,5 @@ module.exports = {
|
||||||
arrowParens: "always",
|
arrowParens: "always",
|
||||||
endOfLine: "auto"
|
endOfLine: "auto"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default config;
|
|
@ -1,6 +1,7 @@
|
||||||
import {app, dialog} from "electron";
|
import {app, dialog} from "electron";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
import type {Settings} from "../types/settings.d.js";
|
||||||
import {getWindowStateLocation} from "./windowState.js";
|
import {getWindowStateLocation} from "./windowState.js";
|
||||||
export let firstRun: boolean;
|
export let firstRun: boolean;
|
||||||
export function checkForDataFolder(): void {
|
export function checkForDataFolder(): void {
|
||||||
|
@ -11,64 +12,32 @@ export function checkForDataFolder(): void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Settings {
|
|
||||||
// Referenced for detecting a broken config.
|
|
||||||
"0"?: string;
|
|
||||||
// Referenced once for disabling mod updating.
|
|
||||||
noBundleUpdates?: boolean;
|
|
||||||
// Only used for external url warning dialog.
|
|
||||||
ignoreProtocolWarning?: boolean;
|
|
||||||
customIcon: string;
|
|
||||||
windowStyle: string;
|
|
||||||
channel: string;
|
|
||||||
armcordCSP: boolean;
|
|
||||||
minimizeToTray: boolean;
|
|
||||||
multiInstance: boolean;
|
|
||||||
spellcheck: boolean;
|
|
||||||
mods: string;
|
|
||||||
dynamicIcon: boolean;
|
|
||||||
mobileMode: boolean;
|
|
||||||
skipSplash: boolean;
|
|
||||||
performanceMode: string;
|
|
||||||
customJsBundle: RequestInfo | URL;
|
|
||||||
customCssBundle: RequestInfo | URL;
|
|
||||||
startMinimized: boolean;
|
|
||||||
useLegacyCapturer: boolean;
|
|
||||||
tray: boolean;
|
|
||||||
keybinds: Array<string>;
|
|
||||||
inviteWebsocket: boolean;
|
|
||||||
disableAutogain: boolean;
|
|
||||||
trayIcon: string;
|
|
||||||
doneSetup: boolean;
|
|
||||||
clientName: string;
|
|
||||||
}
|
|
||||||
export function getConfigLocation(): string {
|
export function getConfigLocation(): string {
|
||||||
const userDataPath = app.getPath("userData");
|
const userDataPath = app.getPath("userData");
|
||||||
const storagePath = path.join(userDataPath, "/storage/");
|
const storagePath = path.join(userDataPath, "/storage/");
|
||||||
return `${storagePath}settings.json`;
|
return `${storagePath}settings.json`;
|
||||||
}
|
}
|
||||||
export async function getConfig<K extends keyof Settings>(object: K): Promise<Settings[K]> {
|
// REVIEW - If I remember correctly fs doesn't need async. I have adjusted the Promise<Settings[K]> to reflect so.
|
||||||
let rawdata = fs.readFileSync(getConfigLocation(), "utf-8");
|
// Why touch it when it worked fine? The Async-ness of this function caused headaches in a lot of other places.
|
||||||
let returndata = JSON.parse(rawdata);
|
// Tested with src/tray.ts - Seems to work great!
|
||||||
return returndata[object];
|
// NOTE - Removed getConfigSync<K extends keyof Settings>(object: K) - Redundant now.
|
||||||
|
export function getConfig<K extends keyof Settings>(object: K): Settings[K] {
|
||||||
|
const rawData = fs.readFileSync(getConfigLocation(), "utf-8");
|
||||||
|
const returnData = JSON.parse(rawData) as Settings;
|
||||||
|
return returnData[object];
|
||||||
}
|
}
|
||||||
export function getConfigSync<K extends keyof Settings>(object: K) {
|
export function setConfig<K extends keyof Settings>(object: K, toSet: Settings[K]): void {
|
||||||
let rawdata = fs.readFileSync(getConfigLocation(), "utf-8");
|
const rawData = fs.readFileSync(getConfigLocation(), "utf-8");
|
||||||
let returndata = JSON.parse(rawdata);
|
const parsed = JSON.parse(rawData) as Settings;
|
||||||
return returndata[object];
|
|
||||||
}
|
|
||||||
export async function setConfig<K extends keyof Settings>(object: K, toSet: Settings[K]): Promise<void> {
|
|
||||||
let rawdata = fs.readFileSync(getConfigLocation(), "utf-8");
|
|
||||||
let parsed = JSON.parse(rawdata);
|
|
||||||
parsed[object] = toSet;
|
parsed[object] = toSet;
|
||||||
let toSave = JSON.stringify(parsed, null, 4);
|
const toSave = JSON.stringify(parsed, null, 4);
|
||||||
fs.writeFileSync(getConfigLocation(), toSave, "utf-8");
|
fs.writeFileSync(getConfigLocation(), toSave, "utf-8");
|
||||||
}
|
}
|
||||||
export async function setConfigBulk(object: Settings): Promise<void> {
|
export function setConfigBulk(object: Settings): void {
|
||||||
let existingData = {};
|
let existingData = {};
|
||||||
try {
|
try {
|
||||||
const existingDataBuffer = fs.readFileSync(getConfigLocation(), "utf-8");
|
const existingDataBuffer = fs.readFileSync(getConfigLocation(), "utf-8");
|
||||||
existingData = JSON.parse(existingDataBuffer.toString());
|
existingData = JSON.parse(existingDataBuffer.toString()) as Settings;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Ignore errors when the file doesn't exist or parsing fails
|
// Ignore errors when the file doesn't exist or parsing fails
|
||||||
}
|
}
|
||||||
|
@ -78,7 +47,7 @@ export async function setConfigBulk(object: Settings): Promise<void> {
|
||||||
const toSave = JSON.stringify(mergedData, null, 4);
|
const toSave = JSON.stringify(mergedData, null, 4);
|
||||||
fs.writeFileSync(getConfigLocation(), toSave, "utf-8");
|
fs.writeFileSync(getConfigLocation(), toSave, "utf-8");
|
||||||
}
|
}
|
||||||
export async function checkIfConfigExists(): Promise<void> {
|
export function checkIfConfigExists(): void {
|
||||||
const userDataPath = app.getPath("userData");
|
const userDataPath = app.getPath("userData");
|
||||||
const storagePath = path.join(userDataPath, "/storage/");
|
const storagePath = path.join(userDataPath, "/storage/");
|
||||||
const settingsFile = `${storagePath}settings.json`;
|
const settingsFile = `${storagePath}settings.json`;
|
||||||
|
@ -91,7 +60,7 @@ export async function checkIfConfigExists(): Promise<void> {
|
||||||
console.log("First run of the ArmCord. Starting setup.");
|
console.log("First run of the ArmCord. Starting setup.");
|
||||||
setup();
|
setup();
|
||||||
firstRun = true;
|
firstRun = true;
|
||||||
} else if ((await getConfig("doneSetup")) == false) {
|
} else if (getConfig("doneSetup") == false) {
|
||||||
console.log("First run of the ArmCord. Starting setup.");
|
console.log("First run of the ArmCord. Starting setup.");
|
||||||
setup();
|
setup();
|
||||||
firstRun = true;
|
firstRun = true;
|
||||||
|
@ -99,9 +68,9 @@ export async function checkIfConfigExists(): Promise<void> {
|
||||||
console.log("ArmCord has been run before. Skipping setup.");
|
console.log("ArmCord has been run before. Skipping setup.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function checkIfConfigIsBroken(): Promise<void> {
|
export function checkIfConfigIsBroken(): void {
|
||||||
try {
|
try {
|
||||||
let settingsData = fs.readFileSync(getConfigLocation(), "utf-8");
|
const settingsData = fs.readFileSync(getConfigLocation(), "utf-8");
|
||||||
JSON.parse(settingsData);
|
JSON.parse(settingsData);
|
||||||
console.log("Config is fine");
|
console.log("Config is fine");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -114,7 +83,7 @@ export async function checkIfConfigIsBroken(): Promise<void> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
let windowData = fs.readFileSync(getWindowStateLocation(), "utf-8");
|
const windowData = fs.readFileSync(getWindowStateLocation(), "utf-8");
|
||||||
JSON.parse(windowData);
|
JSON.parse(windowData);
|
||||||
console.log("Window config is fine");
|
console.log("Window config is fine");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -5,7 +5,7 @@ export function addStyle(styleString: string): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addScript(scriptString: string): void {
|
export function addScript(scriptString: string): void {
|
||||||
let script = document.createElement("script");
|
const script = document.createElement("script");
|
||||||
script.textContent = scriptString;
|
script.textContent = scriptString;
|
||||||
document.body.append(script);
|
document.body.append(script);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {app} from "electron";
|
||||||
import {getConfig} from "./config.js";
|
import {getConfig} from "./config.js";
|
||||||
|
|
||||||
export let transparency: boolean;
|
export let transparency: boolean;
|
||||||
export async function injectElectronFlags(): Promise<void> {
|
export function injectElectronFlags(): void {
|
||||||
// MIT License
|
// MIT License
|
||||||
|
|
||||||
// Copyright (c) 2022 GooseNest
|
// Copyright (c) 2022 GooseNest
|
||||||
|
@ -29,7 +29,7 @@ export async function injectElectronFlags(): Promise<void> {
|
||||||
battery: "--enable-features=TurnOffStreamingMediaCachingOnBattery --force_low_power_gpu", // Known to have better battery life for Chromium?
|
battery: "--enable-features=TurnOffStreamingMediaCachingOnBattery --force_low_power_gpu", // Known to have better battery life for Chromium?
|
||||||
vaapi: "--ignore-gpu-blocklist --enable-features=VaapiVideoDecoder --enable-gpu-rasterization --enable-zero-copy --force_high_performance_gpu --use-gl=desktop --disable-features=UseChromeOSDirectVideoDecoder"
|
vaapi: "--ignore-gpu-blocklist --enable-features=VaapiVideoDecoder --enable-gpu-rasterization --enable-zero-copy --force_high_performance_gpu --use-gl=desktop --disable-features=UseChromeOSDirectVideoDecoder"
|
||||||
};
|
};
|
||||||
switch (await getConfig("performanceMode")) {
|
switch (getConfig("performanceMode")) {
|
||||||
case "performance":
|
case "performance":
|
||||||
console.log("Performance mode enabled");
|
console.log("Performance mode enabled");
|
||||||
app.commandLine.appendArgument(presets.performance);
|
app.commandLine.appendArgument(presets.performance);
|
||||||
|
@ -41,7 +41,7 @@ export async function injectElectronFlags(): Promise<void> {
|
||||||
default:
|
default:
|
||||||
console.log("No performance modes set");
|
console.log("No performance modes set");
|
||||||
}
|
}
|
||||||
if ((await getConfig("windowStyle")) == "transparent" && process.platform === "win32") {
|
if (getConfig("windowStyle") == "transparent" && process.platform === "win32") {
|
||||||
transparency = true;
|
transparency = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,27 @@
|
||||||
import {app} from "electron";
|
import {app} from "electron";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
export async function setLang(language: string): Promise<void> {
|
import {i18nStrings} from "../types/i18nStrings";
|
||||||
|
export function setLang(language: string): void {
|
||||||
const langConfigFile = `${path.join(app.getPath("userData"), "/storage/")}lang.json`;
|
const langConfigFile = `${path.join(app.getPath("userData"), "/storage/")}lang.json`;
|
||||||
if (!fs.existsSync(langConfigFile)) {
|
if (!fs.existsSync(langConfigFile)) {
|
||||||
fs.writeFileSync(langConfigFile, "{}", "utf-8");
|
fs.writeFileSync(langConfigFile, "{}", "utf-8");
|
||||||
}
|
}
|
||||||
let rawdata = fs.readFileSync(langConfigFile, "utf-8");
|
const rawData = fs.readFileSync(langConfigFile, "utf-8");
|
||||||
let parsed = JSON.parse(rawdata);
|
const parsed = JSON.parse(rawData) as i18nStrings;
|
||||||
parsed.lang = language;
|
parsed.lang = language;
|
||||||
let toSave = JSON.stringify(parsed, null, 4);
|
const toSave = JSON.stringify(parsed, null, 4);
|
||||||
fs.writeFileSync(langConfigFile, toSave, "utf-8");
|
fs.writeFileSync(langConfigFile, toSave, "utf-8");
|
||||||
}
|
}
|
||||||
let language: string;
|
let language: string;
|
||||||
export async function getLang(object: string): Promise<string> {
|
export function getLang(object: string): string {
|
||||||
if (language == undefined) {
|
if (language == undefined) {
|
||||||
try {
|
try {
|
||||||
const userDataPath = app.getPath("userData");
|
const userDataPath = app.getPath("userData");
|
||||||
const storagePath = path.join(userDataPath, "/storage/");
|
const storagePath = path.join(userDataPath, "/storage/");
|
||||||
const langConfigFile = `${storagePath}lang.json`;
|
const langConfigFile = `${storagePath}lang.json`;
|
||||||
let rawdata = fs.readFileSync(langConfigFile, "utf-8");
|
const rawData = fs.readFileSync(langConfigFile, "utf-8");
|
||||||
let parsed = JSON.parse(rawdata);
|
const parsed = JSON.parse(rawData) as i18nStrings;
|
||||||
language = parsed.lang;
|
language = parsed.lang;
|
||||||
} catch (_e) {
|
} catch (_e) {
|
||||||
console.log("Language config file doesn't exist. Fallback to English.");
|
console.log("Language config file doesn't exist. Fallback to English.");
|
||||||
|
@ -34,26 +35,26 @@ export async function getLang(object: string): Promise<string> {
|
||||||
if (!fs.existsSync(langPath)) {
|
if (!fs.existsSync(langPath)) {
|
||||||
langPath = path.join(import.meta.dirname, "../", "/assets/lang/en-US.json");
|
langPath = path.join(import.meta.dirname, "../", "/assets/lang/en-US.json");
|
||||||
}
|
}
|
||||||
let rawdata = fs.readFileSync(langPath, "utf-8");
|
let rawData = fs.readFileSync(langPath, "utf-8");
|
||||||
let parsed = JSON.parse(rawdata);
|
let parsed = JSON.parse(rawData) as i18nStrings;
|
||||||
if (parsed[object] == undefined) {
|
if (parsed[object] == undefined) {
|
||||||
console.log(`${object} is undefined in ${language}`);
|
console.log(`${object} is undefined in ${language}`);
|
||||||
langPath = path.join(import.meta.dirname, "../", "/assets/lang/en-US.json");
|
langPath = path.join(import.meta.dirname, "../", "/assets/lang/en-US.json");
|
||||||
rawdata = fs.readFileSync(langPath, "utf-8");
|
rawData = fs.readFileSync(langPath, "utf-8");
|
||||||
parsed = JSON.parse(rawdata);
|
parsed = JSON.parse(rawData) as i18nStrings;
|
||||||
return parsed[object];
|
return parsed[object];
|
||||||
} else {
|
} else {
|
||||||
return parsed[object];
|
return parsed[object];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function getLangName(): Promise<string> {
|
export function getLangName(): string {
|
||||||
if (language == undefined) {
|
if (language == undefined) {
|
||||||
try {
|
try {
|
||||||
const userDataPath = app.getPath("userData");
|
const userDataPath = app.getPath("userData");
|
||||||
const storagePath = path.join(userDataPath, "/storage/");
|
const storagePath = path.join(userDataPath, "/storage/");
|
||||||
const langConfigFile = `${storagePath}lang.json`;
|
const langConfigFile = `${storagePath}lang.json`;
|
||||||
let rawdata = fs.readFileSync(langConfigFile, "utf-8");
|
const rawData = fs.readFileSync(langConfigFile, "utf-8");
|
||||||
let parsed = JSON.parse(rawdata);
|
const parsed = JSON.parse(rawData) as i18nStrings;
|
||||||
language = parsed.lang;
|
language = parsed.lang;
|
||||||
} catch (_e) {
|
} catch (_e) {
|
||||||
console.log("Language config file doesn't exist. Fallback to English.");
|
console.log("Language config file doesn't exist. Fallback to English.");
|
||||||
|
|
|
@ -1,34 +1,31 @@
|
||||||
import {app} from "electron";
|
import {app} from "electron";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
export interface WindowState {
|
import {WindowState} from "../types/windowState";
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
isMaximized: boolean;
|
|
||||||
}
|
|
||||||
export function getWindowStateLocation() {
|
export function getWindowStateLocation() {
|
||||||
const userDataPath = app.getPath("userData");
|
const userDataPath = app.getPath("userData");
|
||||||
const storagePath = path.join(userDataPath, "/storage/");
|
const storagePath = path.join(userDataPath, "/storage/");
|
||||||
return `${storagePath}window.json`;
|
return `${storagePath}window.json`;
|
||||||
}
|
}
|
||||||
export async function setWindowState(object: WindowState): Promise<void> {
|
export function setWindowState(object: WindowState): void {
|
||||||
const userDataPath = app.getPath("userData");
|
const userDataPath = app.getPath("userData");
|
||||||
const storagePath = path.join(userDataPath, "/storage/");
|
const storagePath = path.join(userDataPath, "/storage/");
|
||||||
const saveFile = `${storagePath}window.json`;
|
const saveFile = `${storagePath}window.json`;
|
||||||
let toSave = JSON.stringify(object, null, 4);
|
const toSave = JSON.stringify(object, null, 4);
|
||||||
fs.writeFileSync(saveFile, toSave, "utf-8");
|
fs.writeFileSync(saveFile, toSave, "utf-8");
|
||||||
}
|
}
|
||||||
export async function getWindowState<K extends keyof WindowState>(object: K): Promise<WindowState[K]> {
|
|
||||||
|
// REVIEW - Similar to getConfig, this seems to return a promise when it has no async. Originally Promise<WindowState[K]>
|
||||||
|
|
||||||
|
export function getWindowState<K extends keyof WindowState>(object: K): WindowState[K] {
|
||||||
const userDataPath = app.getPath("userData");
|
const userDataPath = app.getPath("userData");
|
||||||
const storagePath = path.join(userDataPath, "/storage/");
|
const storagePath = path.join(userDataPath, "/storage/");
|
||||||
const settingsFile = `${storagePath}window.json`;
|
const settingsFile = `${storagePath}window.json`;
|
||||||
if (!fs.existsSync(settingsFile)) {
|
if (!fs.existsSync(settingsFile)) {
|
||||||
fs.writeFileSync(settingsFile, "{}", "utf-8");
|
fs.writeFileSync(settingsFile, "{}", "utf-8");
|
||||||
}
|
}
|
||||||
let rawdata = fs.readFileSync(settingsFile, "utf-8");
|
const rawData = fs.readFileSync(settingsFile, "utf-8");
|
||||||
let returndata = JSON.parse(rawdata);
|
const returnData = JSON.parse(rawData) as WindowState;
|
||||||
console.log(`[Window state manager] ${returndata}`);
|
console.log(`[Window state manager] ${JSON.stringify(returnData)}`);
|
||||||
return returndata[object];
|
return returnData[object];
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,9 @@
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
height: 80%;
|
height: 80%;
|
||||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
box-shadow:
|
||||||
|
0 4px 8px 0 rgba(0, 0, 0, 0.2),
|
||||||
|
0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
||||||
-webkit-animation-name: animatetop;
|
-webkit-animation-name: animatetop;
|
||||||
-webkit-animation-duration: 0.4s;
|
-webkit-animation-duration: 0.4s;
|
||||||
animation-name: animatetop;
|
animation-name: animatetop;
|
||||||
|
|
|
@ -47,7 +47,9 @@
|
||||||
.desktop-capturer-selection__btn:hover,
|
.desktop-capturer-selection__btn:hover,
|
||||||
.desktop-capturer-selection__btn:focus {
|
.desktop-capturer-selection__btn:focus {
|
||||||
background: #7289da;
|
background: #7289da;
|
||||||
box-shadow: 0 0 4px rgba(0, 0, 0, 0.45), 0 0 2px rgba(0, 0, 0, 0.25);
|
box-shadow:
|
||||||
|
0 0 4px rgba(0, 0, 0, 0.45),
|
||||||
|
0 0 2px rgba(0, 0, 0, 0.25);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
.desktop-capturer-selection__thumbnail {
|
.desktop-capturer-selection__thumbnail {
|
||||||
|
|
|
@ -50,7 +50,7 @@ function setConstraint(constraint, name, value) {
|
||||||
}
|
}
|
||||||
function disableAutogain(constraints) {
|
function disableAutogain(constraints) {
|
||||||
console.log("Automatically unsetting gain!", constraints);
|
console.log("Automatically unsetting gain!", constraints);
|
||||||
if (constraints && constraints.audio) {
|
if (constraints?.audio) {
|
||||||
if (typeof constraints.audio !== "object") {
|
if (typeof constraints.audio !== "object") {
|
||||||
constraints.audio = {};
|
constraints.audio = {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import electron from "electron";
|
import electron from "electron";
|
||||||
import {getConfig} from "../../common/config.js";
|
import {getConfig} from "../../common/config.js";
|
||||||
|
|
||||||
const unstrictCSP = (): void => {
|
const unrestrictCSP = (): void => {
|
||||||
console.log("Setting up CSP unstricter...");
|
console.log("Setting up CSP unrestricter...");
|
||||||
|
|
||||||
electron.session.defaultSession.webRequest.onHeadersReceived(({responseHeaders, resourceType}, done) => {
|
electron.session.defaultSession.webRequest.onHeadersReceived(({responseHeaders, resourceType}, done) => {
|
||||||
if (!responseHeaders) return done({});
|
if (!responseHeaders) return done({});
|
||||||
|
@ -19,9 +19,10 @@ const unstrictCSP = (): void => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
electron.app.whenReady().then(async () => {
|
void electron.app.whenReady().then(() => {
|
||||||
if (await getConfig("armcordCSP")) {
|
// REVIEW - Awaiting the line above will hang the app.
|
||||||
unstrictCSP();
|
if (getConfig("armcordCSP")) {
|
||||||
|
unrestrictCSP();
|
||||||
} else {
|
} else {
|
||||||
console.log("ArmCord CSP is disabled. The CSP should be managed by a third-party plugin(s).");
|
console.log("ArmCord CSP is disabled. The CSP should be managed by a third-party plugin(s).");
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,23 +3,23 @@ import extract from "extract-zip";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import {getConfig} from "../../common/config.js";
|
import {getConfig} from "../../common/config.js";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import {promisify} from "node:util";
|
import {Readable} from "stream";
|
||||||
import {pipeline} from "stream";
|
import type {ReadableStream} from "stream/web";
|
||||||
const streamPipeline = promisify(pipeline);
|
import {finished} from "stream/promises";
|
||||||
async function updateModBundle(): Promise<void> {
|
async function updateModBundle(): Promise<void> {
|
||||||
if ((await getConfig("noBundleUpdates")) == undefined ?? false) {
|
if (getConfig("noBundleUpdates") == undefined || false) {
|
||||||
try {
|
try {
|
||||||
console.log("Downloading mod bundle");
|
console.log("Downloading mod bundle");
|
||||||
const distFolder = `${app.getPath("userData")}/plugins/loader/dist/`;
|
const distFolder = `${app.getPath("userData")}/plugins/loader/dist/`;
|
||||||
while (!fs.existsSync(distFolder)) {
|
while (!fs.existsSync(distFolder)) {
|
||||||
//waiting
|
//waiting
|
||||||
}
|
}
|
||||||
let name: string = await getConfig("mods");
|
const name: string = getConfig("mods");
|
||||||
if (name == "custom") {
|
if (name == "custom") {
|
||||||
// aspy fix
|
// aspy fix
|
||||||
let bundle: string = await (await fetch(await getConfig("customJsBundle"))).text();
|
const bundle: string = await (await fetch(getConfig("customJsBundle"))).text();
|
||||||
fs.writeFileSync(`${distFolder}bundle.js`, bundle, "utf-8");
|
fs.writeFileSync(`${distFolder}bundle.js`, bundle, "utf-8");
|
||||||
let css: string = await (await fetch(await getConfig("customCssBundle"))).text();
|
const css: string = await (await fetch(getConfig("customCssBundle"))).text();
|
||||||
fs.writeFileSync(`${distFolder}bundle.css`, css, "utf-8");
|
fs.writeFileSync(`${distFolder}bundle.css`, css, "utf-8");
|
||||||
} else {
|
} else {
|
||||||
const clientMods = {
|
const clientMods = {
|
||||||
|
@ -31,9 +31,9 @@ async function updateModBundle(): Promise<void> {
|
||||||
shelter: "https://armcord.app/placeholder.css"
|
shelter: "https://armcord.app/placeholder.css"
|
||||||
};
|
};
|
||||||
console.log(clientMods[name as keyof typeof clientMods]);
|
console.log(clientMods[name as keyof typeof clientMods]);
|
||||||
let bundle: string = await (await fetch(clientMods[name as keyof typeof clientMods])).text();
|
const bundle: string = await (await fetch(clientMods[name as keyof typeof clientMods])).text();
|
||||||
fs.writeFileSync(`${distFolder}bundle.js`, bundle, "utf-8");
|
fs.writeFileSync(`${distFolder}bundle.js`, bundle, "utf-8");
|
||||||
let css: string = await (await fetch(clientModsCss[name as keyof typeof clientModsCss])).text();
|
const css: string = await (await fetch(clientModsCss[name as keyof typeof clientModsCss])).text();
|
||||||
fs.writeFileSync(`${distFolder}bundle.css`, css, "utf-8");
|
fs.writeFileSync(`${distFolder}bundle.css`, css, "utf-8");
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -53,14 +53,14 @@ export let modInstallState: string;
|
||||||
export function updateModInstallState() {
|
export function updateModInstallState() {
|
||||||
modInstallState = "modDownload";
|
modInstallState = "modDownload";
|
||||||
|
|
||||||
updateModBundle();
|
void updateModBundle(); // REVIEW - Awaiting this will hang the app on the splash
|
||||||
import("./plugin.js");
|
import("./plugin.js");
|
||||||
|
|
||||||
modInstallState = "done";
|
modInstallState = "done";
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function installModLoader(): Promise<void> {
|
export async function installModLoader(): Promise<void> {
|
||||||
if ((await getConfig("mods")) == "none") {
|
if (getConfig("mods") == "none") {
|
||||||
modInstallState = "none";
|
modInstallState = "none";
|
||||||
fs.rmSync(`${app.getPath("userData")}/plugins/loader`, {recursive: true, force: true});
|
fs.rmSync(`${app.getPath("userData")}/plugins/loader`, {recursive: true, force: true});
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ export async function installModLoader(): Promise<void> {
|
||||||
fs.rmSync(`${app.getPath("userData")}/plugins/loader`, {recursive: true, force: true});
|
fs.rmSync(`${app.getPath("userData")}/plugins/loader`, {recursive: true, force: true});
|
||||||
modInstallState = "installing";
|
modInstallState = "installing";
|
||||||
|
|
||||||
let zipPath = `${app.getPath("temp")}/loader.zip`;
|
const zipPath = `${app.getPath("temp")}/loader.zip`;
|
||||||
|
|
||||||
if (!fs.existsSync(pluginFolder)) {
|
if (!fs.existsSync(pluginFolder)) {
|
||||||
fs.mkdirSync(pluginFolder);
|
fs.mkdirSync(pluginFolder);
|
||||||
|
@ -88,31 +88,35 @@ export async function installModLoader(): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add more of these later if needed!
|
// Add more of these later if needed!
|
||||||
let URLs = [
|
const URLs = [
|
||||||
"https://armcord.app/loader.zip",
|
"https://armcord.app/loader.zip",
|
||||||
"https://armcord.vercel.app/loader.zip",
|
"https://armcord.vercel.app/loader.zip",
|
||||||
"https://raw.githubusercontent.com/ArmCord/website/new/public/loader.zip"
|
"https://raw.githubusercontent.com/ArmCord/website/new/public/loader.zip"
|
||||||
];
|
];
|
||||||
let loaderZip: any;
|
|
||||||
|
|
||||||
|
// REVIEW - Rewrote this
|
||||||
while (true) {
|
while (true) {
|
||||||
if (URLs.length <= 0) throw new Error(`unexpected response ${loaderZip.statusText}`);
|
let completed = false;
|
||||||
|
await fetch(URLs[0])
|
||||||
try {
|
.then(async (loaderZip) => {
|
||||||
loaderZip = await fetch(URLs[0]);
|
const fileStream = fs.createWriteStream(zipPath);
|
||||||
} catch (err) {
|
await finished(Readable.fromWeb(loaderZip.body as ReadableStream).pipe(fileStream)).then(
|
||||||
console.log("[Mod loader] Failed to download. Links left to try: " + (URLs.length - 1));
|
async () => {
|
||||||
URLs.splice(0, 1);
|
await extract(zipPath, {dir: path.join(app.getPath("userData"), "plugins")}).then(() => {
|
||||||
|
updateModInstallState();
|
||||||
continue;
|
completed = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.warn(`[Mod loader] Failed to download. Links left to try: ${URLs.length - 1}`);
|
||||||
|
URLs.splice(0, 1);
|
||||||
|
});
|
||||||
|
if (completed || URLs.length == 0) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
await streamPipeline(loaderZip.body, fs.createWriteStream(zipPath));
|
|
||||||
await extract(zipPath, {dir: path.join(app.getPath("userData"), "plugins")});
|
|
||||||
|
|
||||||
updateModInstallState();
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("[Mod loader] Failed to install modloader");
|
console.log("[Mod loader] Failed to install modloader");
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import * as fs from "fs";
|
import fs from "fs";
|
||||||
import {app, session} from "electron";
|
import {app, session} from "electron";
|
||||||
const userDataPath = app.getPath("userData");
|
const userDataPath = app.getPath("userData");
|
||||||
const pluginFolder = `${userDataPath}/plugins`;
|
const pluginFolder = `${userDataPath}/plugins`;
|
||||||
|
@ -6,12 +6,13 @@ if (!fs.existsSync(pluginFolder)) {
|
||||||
fs.mkdirSync(pluginFolder);
|
fs.mkdirSync(pluginFolder);
|
||||||
console.log("Created missing plugin folder");
|
console.log("Created missing plugin folder");
|
||||||
}
|
}
|
||||||
app.whenReady().then(() => {
|
await app.whenReady().then(() => {
|
||||||
fs.readdirSync(pluginFolder).forEach((file) => {
|
fs.readdirSync(pluginFolder).forEach((file) => {
|
||||||
try {
|
try {
|
||||||
const manifest = fs.readFileSync(`${pluginFolder}/${file}/manifest.json`, "utf8");
|
const manifest = fs.readFileSync(`${pluginFolder}/${file}/manifest.json`, "utf8");
|
||||||
const pluginFile = JSON.parse(manifest);
|
// NOTE - The below type assertion is just what we need from the chrome manifest
|
||||||
session.defaultSession.loadExtension(`${pluginFolder}/${file}`);
|
const pluginFile = JSON.parse(manifest) as {name: string; author: string};
|
||||||
|
void session.defaultSession.loadExtension(`${pluginFolder}/${file}`); // REVIEW - Awaiting this will cause plugins to not inject
|
||||||
console.log(`[Mod loader] Loaded ${pluginFile.name} made by ${pluginFile.author}`);
|
console.log(`[Mod loader] Loaded ${pluginFile.name} made by ${pluginFile.author}`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
//ipc stuff
|
//ipc stuff
|
||||||
import {app, clipboard, desktopCapturer, ipcMain, nativeImage, shell} from "electron";
|
import {app, clipboard, desktopCapturer, ipcMain, nativeImage, shell, SourcesOptions} from "electron";
|
||||||
import {mainWindow} from "./window.js";
|
import {mainWindow} from "./window.js";
|
||||||
|
|
||||||
import os from "os";
|
import os from "os";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import {getConfig, setConfigBulk, getConfigLocation, Settings} from "../common/config.js";
|
import {getConfig, setConfigBulk, getConfigLocation} from "../common/config.js";
|
||||||
import {setLang, getLang, getLangName} from "../common/lang.js";
|
import {setLang, getLang, getLangName} from "../common/lang.js";
|
||||||
import {sleep} from "../common/sleep.js";
|
|
||||||
import {getVersion, getDisplayVersion} from "../common/version.js";
|
import {getVersion, getDisplayVersion} from "../common/version.js";
|
||||||
import {customTitlebar} from "../main.js";
|
import {customTitlebar} from "../main.js";
|
||||||
import {createSettingsWindow} from "../settings/main.js";
|
import {createSettingsWindow} from "../settings/main.js";
|
||||||
import {splashWindow} from "../splash/main.js";
|
import {splashWindow} from "../splash/main.js";
|
||||||
import {createTManagerWindow} from "../themeManager/main.js";
|
import {createTManagerWindow} from "../themeManager/main.js";
|
||||||
import {modInstallState} from "./extensions/mods.js";
|
import {modInstallState} from "./extensions/mods.js";
|
||||||
|
import {Settings} from "../types/settings.d.js";
|
||||||
|
|
||||||
const userDataPath = app.getPath("userData");
|
const userDataPath = app.getPath("userData");
|
||||||
const storagePath = path.join(userDataPath, "/storage/");
|
const storagePath = path.join(userDataPath, "/storage/");
|
||||||
|
@ -30,7 +30,7 @@ export function registerIpc(): void {
|
||||||
return getLang(toGet);
|
return getLang(toGet);
|
||||||
});
|
});
|
||||||
ipcMain.on("open-external-link", (_event, href: string) => {
|
ipcMain.on("open-external-link", (_event, href: string) => {
|
||||||
shell.openExternal(href);
|
void shell.openExternal(href);
|
||||||
});
|
});
|
||||||
ipcMain.on("setPing", (_event, pingCount: number) => {
|
ipcMain.on("setPing", (_event, pingCount: number) => {
|
||||||
switch (os.platform()) {
|
switch (os.platform()) {
|
||||||
|
@ -39,7 +39,7 @@ export function registerIpc(): void {
|
||||||
break;
|
break;
|
||||||
case "win32":
|
case "win32":
|
||||||
if (pingCount > 0) {
|
if (pingCount > 0) {
|
||||||
let image = nativeImage.createFromPath(path.join(import.meta.dirname, "../", `/assets/ping.png`));
|
const image = nativeImage.createFromPath(path.join(import.meta.dirname, "../", `/assets/ping.png`));
|
||||||
mainWindow.setOverlayIcon(image, "badgeCount");
|
mainWindow.setOverlayIcon(image, "badgeCount");
|
||||||
} else {
|
} else {
|
||||||
mainWindow.setOverlayIcon(null, "badgeCount");
|
mainWindow.setOverlayIcon(null, "badgeCount");
|
||||||
|
@ -80,9 +80,9 @@ export function registerIpc(): void {
|
||||||
ipcMain.on("modInstallState", (event) => {
|
ipcMain.on("modInstallState", (event) => {
|
||||||
event.returnValue = modInstallState;
|
event.returnValue = modInstallState;
|
||||||
});
|
});
|
||||||
ipcMain.on("splashEnd", async () => {
|
ipcMain.on("splashEnd", () => {
|
||||||
splashWindow.close();
|
splashWindow.close();
|
||||||
if (await getConfig("startMinimized")) {
|
if (getConfig("startMinimized")) {
|
||||||
mainWindow.hide();
|
mainWindow.hide();
|
||||||
} else {
|
} else {
|
||||||
mainWindow.show();
|
mainWindow.show();
|
||||||
|
@ -92,75 +92,77 @@ export function registerIpc(): void {
|
||||||
app.relaunch();
|
app.relaunch();
|
||||||
app.exit();
|
app.exit();
|
||||||
});
|
});
|
||||||
ipcMain.on("minimizeToTray", async (event) => {
|
ipcMain.on("saveSettings", (_event, args: Settings) => {
|
||||||
event.returnValue = await getConfig("minimizeToTray");
|
setConfigBulk(args);
|
||||||
});
|
});
|
||||||
ipcMain.on("channel", async (event) => {
|
ipcMain.on("minimizeToTray", (event) => {
|
||||||
event.returnValue = await getConfig("channel");
|
event.returnValue = getConfig("minimizeToTray");
|
||||||
});
|
});
|
||||||
ipcMain.on("clientmod", async (event) => {
|
ipcMain.on("channel", (event) => {
|
||||||
event.returnValue = await getConfig("mods");
|
event.returnValue = getConfig("channel");
|
||||||
});
|
});
|
||||||
ipcMain.on("legacyCapturer", async (event) => {
|
ipcMain.on("clientmod", (event) => {
|
||||||
event.returnValue = await getConfig("useLegacyCapturer");
|
event.returnValue = getConfig("mods");
|
||||||
});
|
});
|
||||||
ipcMain.on("trayIcon", async (event) => {
|
ipcMain.on("legacyCapturer", (event) => {
|
||||||
event.returnValue = await getConfig("trayIcon");
|
event.returnValue = getConfig("useLegacyCapturer");
|
||||||
});
|
});
|
||||||
ipcMain.on("disableAutogain", async (event) => {
|
ipcMain.on("trayIcon", (event) => {
|
||||||
event.returnValue = await getConfig("disableAutogain");
|
event.returnValue = getConfig("trayIcon");
|
||||||
|
});
|
||||||
|
ipcMain.on("disableAutogain", (event) => {
|
||||||
|
event.returnValue = getConfig("disableAutogain");
|
||||||
});
|
});
|
||||||
ipcMain.on("titlebar", (event) => {
|
ipcMain.on("titlebar", (event) => {
|
||||||
event.returnValue = customTitlebar;
|
event.returnValue = customTitlebar;
|
||||||
});
|
});
|
||||||
ipcMain.on("mobileMode", async (event) => {
|
ipcMain.on("mobileMode", (event) => {
|
||||||
event.returnValue = await getConfig("mobileMode");
|
event.returnValue = getConfig("mobileMode");
|
||||||
});
|
});
|
||||||
|
// REVIEW - I don't see a reason to await the actual action of running the settings window. The user cannot open more than one anyway, as defined in the function.
|
||||||
ipcMain.on("openSettingsWindow", () => {
|
ipcMain.on("openSettingsWindow", () => {
|
||||||
createSettingsWindow();
|
void createSettingsWindow();
|
||||||
});
|
});
|
||||||
ipcMain.on("openManagerWindow", () => {
|
ipcMain.on("openManagerWindow", () => {
|
||||||
createTManagerWindow();
|
void createTManagerWindow();
|
||||||
});
|
});
|
||||||
ipcMain.on("setting-armcordCSP", async (event) => {
|
ipcMain.on("setting-armcordCSP", (event) => {
|
||||||
if (await getConfig("armcordCSP")) {
|
if (getConfig("armcordCSP")) {
|
||||||
event.returnValue = true;
|
event.returnValue = true;
|
||||||
} else {
|
} else {
|
||||||
event.returnValue = false;
|
event.returnValue = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ipcMain.handle("DESKTOP_CAPTURER_GET_SOURCES", (_event, opts) => desktopCapturer.getSources(opts));
|
// NOTE - I assume this would return sources based on the fact that the function only ingests sources
|
||||||
|
ipcMain.handle("DESKTOP_CAPTURER_GET_SOURCES", (_event, opts: SourcesOptions) => desktopCapturer.getSources(opts));
|
||||||
ipcMain.on("saveSettings", (_event, args: Settings) => {
|
ipcMain.on("saveSettings", (_event, args: Settings) => {
|
||||||
console.log(args);
|
console.log(args);
|
||||||
setConfigBulk(args);
|
setConfigBulk(args);
|
||||||
});
|
});
|
||||||
ipcMain.on("openStorageFolder", async () => {
|
// REVIEW - The lower 4 functions had await sleep(1000), I'm not sure why. Behavior is same regardless
|
||||||
|
ipcMain.on("openStorageFolder", () => {
|
||||||
shell.showItemInFolder(storagePath);
|
shell.showItemInFolder(storagePath);
|
||||||
await sleep(1000);
|
|
||||||
});
|
});
|
||||||
ipcMain.on("openThemesFolder", async () => {
|
ipcMain.on("openThemesFolder", () => {
|
||||||
shell.showItemInFolder(themesPath);
|
shell.showItemInFolder(themesPath);
|
||||||
await sleep(1000);
|
|
||||||
});
|
});
|
||||||
ipcMain.on("openPluginsFolder", async () => {
|
ipcMain.on("openPluginsFolder", () => {
|
||||||
shell.showItemInFolder(pluginsPath);
|
shell.showItemInFolder(pluginsPath);
|
||||||
await sleep(1000);
|
|
||||||
});
|
});
|
||||||
ipcMain.on("openCrashesFolder", async () => {
|
ipcMain.on("openCrashesFolder", () => {
|
||||||
shell.showItemInFolder(path.join(app.getPath("temp"), `${app.getName()} Crashes`));
|
shell.showItemInFolder(path.join(app.getPath("temp"), `${app.getName()} Crashes`));
|
||||||
await sleep(1000);
|
|
||||||
});
|
});
|
||||||
ipcMain.on("getLangName", async (event) => {
|
ipcMain.on("getLangName", (event) => {
|
||||||
event.returnValue = await getLangName();
|
event.returnValue = getLangName();
|
||||||
});
|
});
|
||||||
ipcMain.on("crash", async () => {
|
ipcMain.on("crash", () => {
|
||||||
process.crash();
|
process.crash();
|
||||||
});
|
});
|
||||||
ipcMain.handle("getSetting", (_event, toGet: keyof Settings) => {
|
ipcMain.handle("getSetting", (_event, toGet: keyof Settings) => {
|
||||||
return getConfig(toGet);
|
return getConfig(toGet);
|
||||||
});
|
});
|
||||||
ipcMain.on("copyDebugInfo", () => {
|
ipcMain.on("copyDebugInfo", () => {
|
||||||
let settingsFileContent = fs.readFileSync(getConfigLocation(), "utf-8");
|
const settingsFileContent = fs.readFileSync(getConfigLocation(), "utf-8");
|
||||||
clipboard.writeText(
|
clipboard.writeText(
|
||||||
`**OS:** ${os.platform()} ${os.version()}\n**Architecture:** ${os.arch()}\n**ArmCord version:** ${getVersion()}\n**Electron version:** ${
|
`**OS:** ${os.platform()} ${os.version()}\n**Architecture:** ${os.arch()}\n**ArmCord version:** ${getVersion()}\n**Electron version:** ${
|
||||||
process.versions.electron
|
process.versions.electron
|
||||||
|
|
|
@ -1,17 +1,9 @@
|
||||||
import {BrowserWindow, Menu, app, clipboard} from "electron";
|
import {BrowserWindow, Menu, app} from "electron";
|
||||||
import {mainWindow} from "./window.js";
|
import {mainWindow} from "./window.js";
|
||||||
import {createSettingsWindow} from "../settings/main.js";
|
import {createSettingsWindow} from "../settings/main.js";
|
||||||
|
|
||||||
function paste(contents: any): void {
|
export function setMenu(): void {
|
||||||
const contentTypes = clipboard.availableFormats().toString();
|
const template: Electron.MenuItemConstructorOptions[] = [
|
||||||
//Workaround: fix pasting the images.
|
|
||||||
if (contentTypes.includes("image/") && contentTypes.includes("text/html")) {
|
|
||||||
clipboard.writeImage(clipboard.readImage());
|
|
||||||
}
|
|
||||||
contents.paste();
|
|
||||||
}
|
|
||||||
export async function setMenu(): Promise<void> {
|
|
||||||
let template: Electron.MenuItemConstructorOptions[] = [
|
|
||||||
{
|
{
|
||||||
label: "ArmCord",
|
label: "ArmCord",
|
||||||
submenu: [
|
submenu: [
|
||||||
|
@ -28,7 +20,7 @@ export async function setMenu(): Promise<void> {
|
||||||
label: "Open settings",
|
label: "Open settings",
|
||||||
accelerator: "CmdOrCtrl+Shift+'",
|
accelerator: "CmdOrCtrl+Shift+'",
|
||||||
click() {
|
click() {
|
||||||
createSettingsWindow();
|
void createSettingsWindow();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -67,13 +59,6 @@ export async function setMenu(): Promise<void> {
|
||||||
{type: "separator"},
|
{type: "separator"},
|
||||||
{label: "Cut", accelerator: "CmdOrCtrl+X", role: "cut"},
|
{label: "Cut", accelerator: "CmdOrCtrl+X", role: "cut"},
|
||||||
{label: "Copy", accelerator: "CmdOrCtrl+C", role: "copy"},
|
{label: "Copy", accelerator: "CmdOrCtrl+C", role: "copy"},
|
||||||
{
|
|
||||||
label: "Paste",
|
|
||||||
accelerator: "CmdOrCtrl+V",
|
|
||||||
click() {
|
|
||||||
paste(mainWindow.webContents);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{label: "Select All", accelerator: "CmdOrCtrl+A", role: "selectAll"}
|
{label: "Select All", accelerator: "CmdOrCtrl+A", role: "selectAll"}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import {contextBridge, ipcRenderer} from "electron";
|
import {contextBridge, ipcRenderer, type SourcesOptions} from "electron";
|
||||||
import {injectTitlebar} from "./titlebar.mjs";
|
import {injectTitlebar} from "./titlebar.mjs";
|
||||||
|
import type {ArmCordWindow} from "../../types/armcordWindow.d.js";
|
||||||
const CANCEL_ID = "desktop-capturer-selection__cancel";
|
const CANCEL_ID = "desktop-capturer-selection__cancel";
|
||||||
const desktopCapturer = {
|
const desktopCapturer = {
|
||||||
getSources: (opts: any) => ipcRenderer.invoke("DESKTOP_CAPTURER_GET_SOURCES", opts)
|
getSources: (opts: SourcesOptions) => ipcRenderer.invoke("DESKTOP_CAPTURER_GET_SOURCES", opts)
|
||||||
};
|
};
|
||||||
interface IPCSources {
|
interface IPCSources {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -10,9 +11,9 @@ interface IPCSources {
|
||||||
thumbnail: HTMLCanvasElement;
|
thumbnail: HTMLCanvasElement;
|
||||||
}
|
}
|
||||||
async function getDisplayMediaSelector(): Promise<string> {
|
async function getDisplayMediaSelector(): Promise<string> {
|
||||||
const sources: IPCSources[] = await desktopCapturer.getSources({
|
const sources = (await desktopCapturer.getSources({
|
||||||
types: ["screen", "window"]
|
types: ["screen", "window"]
|
||||||
});
|
})) as IPCSources[];
|
||||||
return `<div class="desktop-capturer-selection__scroller">
|
return `<div class="desktop-capturer-selection__scroller">
|
||||||
<ul class="desktop-capturer-selection__list">
|
<ul class="desktop-capturer-selection__list">
|
||||||
${sources
|
${sources
|
||||||
|
@ -44,24 +45,25 @@ contextBridge.exposeInMainWorld("armcord", {
|
||||||
},
|
},
|
||||||
titlebar: {
|
titlebar: {
|
||||||
injectTitlebar: () => injectTitlebar(),
|
injectTitlebar: () => injectTitlebar(),
|
||||||
isTitlebar: ipcRenderer.sendSync("titlebar")
|
isTitlebar: ipcRenderer.sendSync("titlebar") as boolean
|
||||||
},
|
},
|
||||||
electron: process.versions.electron,
|
electron: process.versions.electron,
|
||||||
channel: ipcRenderer.sendSync("channel"),
|
channel: ipcRenderer.sendSync("channel") as string,
|
||||||
setPingCount: (pingCount: number) => ipcRenderer.send("setPing", pingCount),
|
setPingCount: (pingCount: number) => ipcRenderer.send("setPing", pingCount),
|
||||||
setTrayIcon: (favicon: string) => ipcRenderer.send("sendTrayIcon", favicon),
|
setTrayIcon: (favicon: string) => ipcRenderer.send("sendTrayIcon", favicon),
|
||||||
getLang: (toGet: string) =>
|
getLang: (toGet: string) =>
|
||||||
ipcRenderer.invoke("getLang", toGet).then((result) => {
|
ipcRenderer.invoke("getLang", toGet).then((result) => {
|
||||||
return result;
|
return result as string;
|
||||||
}),
|
}),
|
||||||
getDisplayMediaSelector,
|
getDisplayMediaSelector,
|
||||||
version: ipcRenderer.sendSync("get-app-version", "app-version"),
|
version: ipcRenderer.sendSync("get-app-version", "app-version") as string,
|
||||||
mods: ipcRenderer.sendSync("clientmod"),
|
mods: ipcRenderer.sendSync("clientmod") as string,
|
||||||
openSettingsWindow: () => ipcRenderer.send("openSettingsWindow")
|
openSettingsWindow: () => ipcRenderer.send("openSettingsWindow")
|
||||||
});
|
} as ArmCordWindow);
|
||||||
let windowCallback: (arg0: object) => void;
|
let windowCallback: (arg0: object) => void;
|
||||||
contextBridge.exposeInMainWorld("ArmCordRPC", {
|
contextBridge.exposeInMainWorld("ArmCordRPC", {
|
||||||
listen: (callback: any) => {
|
// REVIEW - I don't think this is right
|
||||||
|
listen: (callback: () => void) => {
|
||||||
windowCallback = callback;
|
windowCallback = callback;
|
||||||
}
|
}
|
||||||
});
|
});
|
|
@ -1,6 +1,6 @@
|
||||||
import {addStyle} from "../../common/dom.js";
|
import {addStyle} from "../../common/dom.js";
|
||||||
import * as fs from "fs";
|
import fs from "fs";
|
||||||
import * as path from "path";
|
import path from "path";
|
||||||
export function injectMobileStuff(): void {
|
export function injectMobileStuff(): void {
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
const mobileCSS = path.join(import.meta.dirname, "../", "/content/css/mobile.css");
|
const mobileCSS = path.join(import.meta.dirname, "../", "/content/css/mobile.css");
|
|
@ -1,9 +0,0 @@
|
||||||
const optimize = (orig: Function) =>
|
|
||||||
function (this: any, ...args: any[]) {
|
|
||||||
if (typeof args[0]?.className === "string" && args[0].className.indexOf("activity") !== -1)
|
|
||||||
return setTimeout(() => orig.apply(this, args), 100);
|
|
||||||
|
|
||||||
return orig.apply(this, args);
|
|
||||||
};
|
|
||||||
|
|
||||||
Element.prototype.removeChild = optimize(Element.prototype.removeChild);
|
|
18
src/discord/preload/optimizer.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
type OptimizableFunction<T extends Node> = (child: T) => T;
|
||||||
|
|
||||||
|
const optimize = <T extends Node>(orig: OptimizableFunction<T>) => {
|
||||||
|
return function (this: Element, ...args: [Element]) {
|
||||||
|
if (typeof args[0]?.className === "string" && args[0].className.indexOf("activity") !== -1) {
|
||||||
|
// @ts-expect-error - // FIXME
|
||||||
|
return setTimeout(() => orig.apply(this, args), 100);
|
||||||
|
}
|
||||||
|
// @ts-expect-error - // FIXME
|
||||||
|
return orig.apply(this, args);
|
||||||
|
} as unknown as OptimizableFunction<T>;
|
||||||
|
};
|
||||||
|
|
||||||
|
// We are taking in the function itself
|
||||||
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||||
|
Element.prototype.removeChild = optimize(Element.prototype.removeChild);
|
||||||
|
|
||||||
|
// Thanks Ari - <@1249446413952225452>
|
|
@ -1,36 +1,39 @@
|
||||||
import "./bridge.mjs";
|
import "./bridge.js";
|
||||||
import "./optimizer.mjs";
|
import "./optimizer.js";
|
||||||
import "./settings.mjs";
|
import "./settings.js";
|
||||||
import {ipcRenderer} from "electron";
|
import {ipcRenderer} from "electron";
|
||||||
import * as fs from "fs";
|
import fs from "fs";
|
||||||
import * as path from "path";
|
import path from "path";
|
||||||
import {injectMobileStuff} from "./mobile.mjs";
|
import {injectMobileStuff} from "./mobile.js";
|
||||||
import {fixTitlebar, injectTitlebar} from "./titlebar.mjs";
|
import {fixTitlebar, injectTitlebar} from "./titlebar.mjs";
|
||||||
import {injectSettings} from "./settings.mjs";
|
import {injectSettings} from "./settings.js";
|
||||||
import {addStyle, addScript} from "../../common/dom.js";
|
import {addStyle, addScript} from "../../common/dom.js";
|
||||||
import {sleep} from "../../common/sleep.js";
|
import {sleep} from "../../common/sleep.js";
|
||||||
|
import type {ArmCordWindow} from "../../types/armcordWindow.d.js";
|
||||||
|
|
||||||
window.localStorage.setItem("hideNag", "true");
|
window.localStorage.setItem("hideNag", "true");
|
||||||
|
|
||||||
if (ipcRenderer.sendSync("legacyCapturer")) {
|
if (ipcRenderer.sendSync("legacyCapturer")) {
|
||||||
console.warn("Using legacy capturer module");
|
console.warn("Using legacy capturer module");
|
||||||
import("./capturer.mjs");
|
import("./capturer.js");
|
||||||
}
|
}
|
||||||
|
|
||||||
const version = ipcRenderer.sendSync("displayVersion");
|
const version = ipcRenderer.sendSync("displayVersion") as string;
|
||||||
async function updateLang(): Promise<void> {
|
function updateLang(): void {
|
||||||
const value = `; ${document.cookie}`;
|
const value = `; ${document.cookie}`;
|
||||||
const parts: any = value.split(`; locale=`);
|
const parts = value.split(`; locale=`);
|
||||||
if (parts.length === 2) ipcRenderer.send("setLang", parts.pop().split(";").shift());
|
if (parts.length === 2) ipcRenderer.send("setLang", parts.pop()?.split(";").shift());
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
armcord: any;
|
// REVIEW - Assumption, this was previously any
|
||||||
|
armcord: ArmCordWindow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`ArmCord ${version}`);
|
console.log(`ArmCord ${version}`);
|
||||||
ipcRenderer.on("themeLoader", (_event, message) => {
|
ipcRenderer.on("themeLoader", (_event, message: string) => {
|
||||||
addStyle(message);
|
addStyle(message);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -40,7 +43,7 @@ if (ipcRenderer.sendSync("titlebar")) {
|
||||||
if (ipcRenderer.sendSync("mobileMode")) {
|
if (ipcRenderer.sendSync("mobileMode")) {
|
||||||
injectMobileStuff();
|
injectMobileStuff();
|
||||||
}
|
}
|
||||||
sleep(5000).then(async () => {
|
await sleep(5000).then(() => {
|
||||||
// dirty hack to make clicking notifications focus ArmCord
|
// dirty hack to make clicking notifications focus ArmCord
|
||||||
addScript(`
|
addScript(`
|
||||||
(() => {
|
(() => {
|
||||||
|
@ -62,7 +65,7 @@ sleep(5000).then(async () => {
|
||||||
addScript(fs.readFileSync(path.join(import.meta.dirname, "../", "/content/js/rpc.js"), "utf8"));
|
addScript(fs.readFileSync(path.join(import.meta.dirname, "../", "/content/js/rpc.js"), "utf8"));
|
||||||
const cssPath = path.join(import.meta.dirname, "../", "/content/css/discord.css");
|
const cssPath = path.join(import.meta.dirname, "../", "/content/css/discord.css");
|
||||||
addStyle(fs.readFileSync(cssPath, "utf8"));
|
addStyle(fs.readFileSync(cssPath, "utf8"));
|
||||||
await updateLang();
|
updateLang();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Settings info version injection
|
// Settings info version injection
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import * as path from "path";
|
import path from "path";
|
||||||
import * as fs from "fs";
|
import fs from "fs";
|
||||||
import {addStyle} from "../../common/dom.js";
|
import {addStyle} from "../../common/dom.js";
|
||||||
import {WebviewTag} from "electron";
|
|
||||||
|
|
||||||
var webview = `<webview src="${path.join(
|
const webview = `<webview src="${path.join(
|
||||||
"file://",
|
"file://",
|
||||||
import.meta.dirname,
|
import.meta.dirname,
|
||||||
"../",
|
"../",
|
||||||
|
@ -25,8 +24,8 @@ export function injectSettings() {
|
||||||
document.addEventListener("DOMContentLoaded", function (_event) {
|
document.addEventListener("DOMContentLoaded", function (_event) {
|
||||||
const settingsCssPath = path.join(import.meta.dirname, "../", "/content/css/inAppSettings.css");
|
const settingsCssPath = path.join(import.meta.dirname, "../", "/content/css/inAppSettings.css");
|
||||||
addStyle(fs.readFileSync(settingsCssPath, "utf8"));
|
addStyle(fs.readFileSync(settingsCssPath, "utf8"));
|
||||||
const webview = document.querySelector("webview") as WebviewTag;
|
const webview = document.querySelector("webview")!;
|
||||||
webview.addEventListener("console-message", (e) => {
|
webview.addEventListener("console-message", (e) => {
|
||||||
console.log("Settings page logged a message:", e.message);
|
console.log("Settings page logged a message:", e as Electron.ConsoleMessageEvent);
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -1,7 +1,7 @@
|
||||||
import {ipcRenderer} from "electron";
|
import {ipcRenderer} from "electron";
|
||||||
import {addStyle} from "../../common/dom.js";
|
import {addStyle} from "../../common/dom.js";
|
||||||
import * as fs from "fs";
|
import fs from "fs";
|
||||||
import * as path from "path";
|
import path from "path";
|
||||||
import os from "os";
|
import os from "os";
|
||||||
export function injectTitlebar(): void {
|
export function injectTitlebar(): void {
|
||||||
document.addEventListener("DOMContentLoaded", function (_event) {
|
document.addEventListener("DOMContentLoaded", function (_event) {
|
||||||
|
|
|
@ -12,7 +12,7 @@ function showAudioDialog(): boolean {
|
||||||
detail: "Selecting yes will make viewers of your stream hear your entire system audio."
|
detail: "Selecting yes will make viewers of your stream hear your entire system audio."
|
||||||
};
|
};
|
||||||
|
|
||||||
dialog.showMessageBox(capturerWindow, options).then(({response}) => {
|
void dialog.showMessageBox(capturerWindow, options).then(({response}) => {
|
||||||
if (response == 0) {
|
if (response == 0) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -23,48 +23,51 @@ function showAudioDialog(): boolean {
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerCustomHandler(): void {
|
function registerCustomHandler(): void {
|
||||||
session.defaultSession.setDisplayMediaRequestHandler(async (request, callback) => {
|
session.defaultSession.setDisplayMediaRequestHandler((request, callback) => {
|
||||||
console.log(request);
|
console.log(request);
|
||||||
const sources = await desktopCapturer.getSources({
|
void desktopCapturer
|
||||||
types: ["screen", "window"]
|
.getSources({
|
||||||
});
|
types: ["screen", "window"]
|
||||||
console.log(sources);
|
})
|
||||||
if (process.platform === "linux" && process.env.XDG_SESSION_TYPE?.toLowerCase() === "wayland") {
|
.then((sources) => {
|
||||||
console.log("WebRTC Capturer detected, skipping window creation."); //assume webrtc capturer is used
|
console.log(sources);
|
||||||
var options: Electron.Streams = {video: sources[0]};
|
if (process.platform === "linux" && process.env.XDG_SESSION_TYPE?.toLowerCase() === "wayland") {
|
||||||
if (showAudioDialog() == true) options = {video: sources[0], audio: "loopbackWithMute"};
|
console.log("WebRTC Capturer detected, skipping window creation."); //assume webrtc capturer is used
|
||||||
callback(options);
|
let options: Electron.Streams = {video: sources[0]};
|
||||||
} else {
|
|
||||||
capturerWindow = new BrowserWindow({
|
|
||||||
width: 800,
|
|
||||||
height: 600,
|
|
||||||
title: "ArmCord Screenshare",
|
|
||||||
darkTheme: true,
|
|
||||||
icon: iconPath,
|
|
||||||
frame: true,
|
|
||||||
autoHideMenuBar: true,
|
|
||||||
webPreferences: {
|
|
||||||
sandbox: false,
|
|
||||||
spellcheck: false,
|
|
||||||
preload: path.join(import.meta.dirname, "preload.js")
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ipcMain.once("selectScreenshareSource", (_event, id, name) => {
|
|
||||||
//console.log(sources[id]);
|
|
||||||
//console.log(id);
|
|
||||||
capturerWindow.close();
|
|
||||||
let result = {id, name};
|
|
||||||
if (process.platform === "linux" || process.platform === "win32") {
|
|
||||||
var options: Electron.Streams = {video: sources[0]};
|
|
||||||
if (showAudioDialog() == true) options = {video: sources[0], audio: "loopbackWithMute"};
|
if (showAudioDialog() == true) options = {video: sources[0], audio: "loopbackWithMute"};
|
||||||
callback(options);
|
callback(options);
|
||||||
} else {
|
} else {
|
||||||
callback({video: result});
|
capturerWindow = new BrowserWindow({
|
||||||
|
width: 800,
|
||||||
|
height: 600,
|
||||||
|
title: "ArmCord Screenshare",
|
||||||
|
darkTheme: true,
|
||||||
|
icon: iconPath,
|
||||||
|
frame: true,
|
||||||
|
autoHideMenuBar: true,
|
||||||
|
webPreferences: {
|
||||||
|
sandbox: false,
|
||||||
|
spellcheck: false,
|
||||||
|
preload: path.join(import.meta.dirname, "preload.mjs")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ipcMain.once("selectScreenshareSource", (_event, id: string, name: string) => {
|
||||||
|
//console.log(sources[id]);
|
||||||
|
//console.log(id);
|
||||||
|
capturerWindow.close();
|
||||||
|
const result = {id, name};
|
||||||
|
if (process.platform === "linux" || process.platform === "win32") {
|
||||||
|
let options: Electron.Streams = {video: sources[0]};
|
||||||
|
if (showAudioDialog() == true) options = {video: sources[0], audio: "loopbackWithMute"};
|
||||||
|
callback(options);
|
||||||
|
} else {
|
||||||
|
callback({video: result});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
void capturerWindow.loadURL(`file://${import.meta.dirname}/picker.html`);
|
||||||
|
capturerWindow.webContents.send("getSources", sources);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
capturerWindow.loadURL(`file://${import.meta.dirname}/picker.html`);
|
|
||||||
capturerWindow.webContents.send("getSources", sources);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
registerCustomHandler();
|
registerCustomHandler();
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
|
|
@ -4,9 +4,9 @@ interface IPCSources {
|
||||||
name: string;
|
name: string;
|
||||||
thumbnail: HTMLCanvasElement;
|
thumbnail: HTMLCanvasElement;
|
||||||
}
|
}
|
||||||
async function addDisplays(): Promise<void> {
|
function addDisplays(): void {
|
||||||
ipcRenderer.once("getSources", (_event, arg) => {
|
ipcRenderer.once("getSources", (_event, arg: IPCSources[]) => {
|
||||||
let sources: IPCSources[] = arg;
|
const sources = arg;
|
||||||
console.log(sources);
|
console.log(sources);
|
||||||
const selectionElem = document.createElement("div");
|
const selectionElem = document.createElement("div");
|
||||||
selectionElem.classList.add("desktop-capturer-selection");
|
selectionElem.classList.add("desktop-capturer-selection");
|
||||||
|
@ -33,7 +33,7 @@ async function addDisplays(): Promise<void> {
|
||||||
</div>`;
|
</div>`;
|
||||||
document.body.appendChild(selectionElem);
|
document.body.appendChild(selectionElem);
|
||||||
document.querySelectorAll(".desktop-capturer-selection__btn").forEach((button) => {
|
document.querySelectorAll(".desktop-capturer-selection__btn").forEach((button) => {
|
||||||
button.addEventListener("click", async () => {
|
button.addEventListener("click", () => {
|
||||||
try {
|
try {
|
||||||
const id = button.getAttribute("data-id");
|
const id = button.getAttribute("data-id");
|
||||||
const title = button.getAttribute("title");
|
const title = button.getAttribute("title");
|
|
@ -1,9 +1,11 @@
|
||||||
// To allow seamless switching between custom titlebar and native os titlebar,
|
// To allow seamless switching between custom titlebar and native os titlebar,
|
||||||
// I had to add most of the window creation code here to split both into seperete functions
|
// I had to add most of the window creation code here to split both into separate functions
|
||||||
// WHY? Because I can't use the same code for both due to annoying bug with value `frame` not responding to variables
|
// WHY? Because I can't use the same code for both due to annoying bug with value `frame` not responding to variables
|
||||||
// I'm sorry for this mess but I'm not sure how to fix it.
|
// I'm sorry for this mess but I'm not sure how to fix it.
|
||||||
import {BrowserWindow, MessageBoxOptions, app, dialog, nativeImage, shell} from "electron";
|
import {BrowserWindow, MessageBoxOptions, app, dialog, nativeImage, shell} from "electron";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
import type EventEmitter from "events";
|
||||||
|
import {ThemeManifest} from "../types/themeManifest.d.js";
|
||||||
import {registerIpc} from "./ipc.js";
|
import {registerIpc} from "./ipc.js";
|
||||||
import {setMenu} from "./menu.js";
|
import {setMenu} from "./menu.js";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
|
@ -28,7 +30,7 @@ contextMenu({
|
||||||
// Only show it when right-clicking text
|
// Only show it when right-clicking text
|
||||||
visible: parameters.selectionText.trim().length > 0,
|
visible: parameters.selectionText.trim().length > 0,
|
||||||
click: () => {
|
click: () => {
|
||||||
shell.openExternal(`https://google.com/search?q=${encodeURIComponent(parameters.selectionText)}`);
|
void shell.openExternal(`https://google.com/search?q=${encodeURIComponent(parameters.selectionText)}`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -36,27 +38,30 @@ contextMenu({
|
||||||
// Only show it when right-clicking text
|
// Only show it when right-clicking text
|
||||||
visible: parameters.selectionText.trim().length > 0,
|
visible: parameters.selectionText.trim().length > 0,
|
||||||
click: () => {
|
click: () => {
|
||||||
shell.openExternal(`https://duckduckgo.com/?q=${encodeURIComponent(parameters.selectionText)}`);
|
void shell.openExternal(`https://duckduckgo.com/?q=${encodeURIComponent(parameters.selectionText)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
async function doAfterDefiningTheWindow(): Promise<void> {
|
function doAfterDefiningTheWindow(): void {
|
||||||
if ((await getWindowState("isMaximized")) ?? false) {
|
if (getWindowState("isMaximized") ?? false) {
|
||||||
mainWindow.setSize(835, 600); //just so the whole thing doesn't cover whole screen
|
mainWindow.setSize(835, 600); //just so the whole thing doesn't cover whole screen
|
||||||
mainWindow.maximize();
|
mainWindow.maximize();
|
||||||
mainWindow.webContents.executeJavaScript(`document.body.setAttribute("isMaximized", "");`);
|
void mainWindow.webContents.executeJavaScript(`document.body.setAttribute("isMaximized", "");`);
|
||||||
mainWindow.hide(); // please don't flashbang the user
|
mainWindow.hide(); // please don't flashbang the user
|
||||||
}
|
}
|
||||||
if ((await getConfig("windowStyle")) == "transparency" && process.platform === "win32") {
|
if (getConfig("windowStyle") == "transparency" && process.platform === "win32") {
|
||||||
mainWindow.setBackgroundMaterial("mica");
|
mainWindow.setBackgroundMaterial("mica");
|
||||||
if ((await getConfig("startMinimized")) == false) {
|
if (getConfig("startMinimized") == false) {
|
||||||
mainWindow.show();
|
mainWindow.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let ignoreProtocolWarning = await getConfig("ignoreProtocolWarning");
|
|
||||||
|
// REVIEW - Test the protocol warning. I was not sure how to get it to pop up. For now I've voided the promises.
|
||||||
|
|
||||||
|
const ignoreProtocolWarning = getConfig("ignoreProtocolWarning");
|
||||||
registerIpc();
|
registerIpc();
|
||||||
if (await getConfig("mobileMode")) {
|
if (getConfig("mobileMode")) {
|
||||||
mainWindow.webContents.userAgent =
|
mainWindow.webContents.userAgent =
|
||||||
"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.149 Mobile Safari/537.36";
|
"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.149 Mobile Safari/537.36";
|
||||||
} else {
|
} else {
|
||||||
|
@ -96,9 +101,9 @@ async function doAfterDefiningTheWindow(): Promise<void> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (url.startsWith("https:") || url.startsWith("http:") || url.startsWith("mailto:")) {
|
if (url.startsWith("https:") || url.startsWith("http:") || url.startsWith("mailto:")) {
|
||||||
shell.openExternal(url);
|
void shell.openExternal(url);
|
||||||
} else if (ignoreProtocolWarning) {
|
} else if (ignoreProtocolWarning) {
|
||||||
shell.openExternal(url);
|
void shell.openExternal(url);
|
||||||
} else {
|
} else {
|
||||||
const options: MessageBoxOptions = {
|
const options: MessageBoxOptions = {
|
||||||
type: "question",
|
type: "question",
|
||||||
|
@ -111,7 +116,7 @@ async function doAfterDefiningTheWindow(): Promise<void> {
|
||||||
checkboxChecked: false
|
checkboxChecked: false
|
||||||
};
|
};
|
||||||
|
|
||||||
dialog.showMessageBox(mainWindow, options).then(({response, checkboxChecked}) => {
|
void dialog.showMessageBox(mainWindow, options).then(({response, checkboxChecked}) => {
|
||||||
console.log(response, checkboxChecked);
|
console.log(response, checkboxChecked);
|
||||||
if (checkboxChecked) {
|
if (checkboxChecked) {
|
||||||
if (response == 0) {
|
if (response == 0) {
|
||||||
|
@ -121,13 +126,13 @@ async function doAfterDefiningTheWindow(): Promise<void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (response == 0) {
|
if (response == 0) {
|
||||||
shell.openExternal(url);
|
void shell.openExternal(url);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return {action: "deny"};
|
return {action: "deny"};
|
||||||
});
|
});
|
||||||
if ((await getConfig("useLegacyCapturer")) == false) {
|
if (getConfig("useLegacyCapturer") == false) {
|
||||||
console.log("Starting screenshare module...");
|
console.log("Starting screenshare module...");
|
||||||
import("./screenshare/main.js");
|
import("./screenshare/main.js");
|
||||||
}
|
}
|
||||||
|
@ -137,9 +142,12 @@ async function doAfterDefiningTheWindow(): Promise<void> {
|
||||||
(_, callback) => callback({cancel: true})
|
(_, callback) => callback({cancel: true})
|
||||||
);
|
);
|
||||||
|
|
||||||
if ((await getConfig("trayIcon")) == "default" || (await getConfig("dynamicIcon"))) {
|
if (getConfig("trayIcon") == "default" || getConfig("dynamicIcon")) {
|
||||||
mainWindow.webContents.on("page-favicon-updated", async () => {
|
mainWindow.webContents.on("page-favicon-updated", () => {
|
||||||
let faviconBase64 = await mainWindow.webContents.executeJavaScript(`
|
// REVIEW - no need to await if we just .then() - This works!
|
||||||
|
void mainWindow.webContents
|
||||||
|
.executeJavaScript(
|
||||||
|
`
|
||||||
var getFavicon = function(){
|
var getFavicon = function(){
|
||||||
var favicon = undefined;
|
var favicon = undefined;
|
||||||
var nodeList = document.getElementsByTagName("link");
|
var nodeList = document.getElementsByTagName("link");
|
||||||
|
@ -153,29 +161,33 @@ async function doAfterDefiningTheWindow(): Promise<void> {
|
||||||
return favicon;
|
return favicon;
|
||||||
}
|
}
|
||||||
getFavicon()
|
getFavicon()
|
||||||
`);
|
`
|
||||||
let buf = Buffer.from(faviconBase64.replace(/^data:image\/\w+;base64,/, ""), "base64");
|
)
|
||||||
fs.writeFileSync(path.join(app.getPath("temp"), "/", "tray.png"), buf, "utf-8");
|
.then((faviconBase64: string) => {
|
||||||
let trayPath = nativeImage.createFromPath(path.join(app.getPath("temp"), "/", "tray.png"));
|
const buf = Buffer.from(faviconBase64.replace(/^data:image\/\w+;base64,/, ""), "base64");
|
||||||
if (process.platform === "darwin" && trayPath.getSize().height > 22)
|
fs.writeFileSync(path.join(app.getPath("temp"), "/", "tray.png"), buf, "utf-8");
|
||||||
trayPath = trayPath.resize({height: 22});
|
let trayPath = nativeImage.createFromPath(path.join(app.getPath("temp"), "/", "tray.png"));
|
||||||
if (process.platform === "win32" && trayPath.getSize().height > 32)
|
if (process.platform === "darwin" && trayPath.getSize().height > 22)
|
||||||
trayPath = trayPath.resize({height: 32});
|
trayPath = trayPath.resize({height: 22});
|
||||||
if (await getConfig("tray")) {
|
if (process.platform === "win32" && trayPath.getSize().height > 32)
|
||||||
if ((await getConfig("trayIcon")) == "default") {
|
trayPath = trayPath.resize({height: 32});
|
||||||
tray.setImage(trayPath);
|
if (getConfig("tray")) {
|
||||||
}
|
if (getConfig("trayIcon") == "default") {
|
||||||
}
|
tray.setImage(trayPath);
|
||||||
if (await getConfig("dynamicIcon")) {
|
}
|
||||||
mainWindow.setIcon(trayPath);
|
}
|
||||||
}
|
if (getConfig("dynamicIcon")) {
|
||||||
|
mainWindow.setIcon(trayPath);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
mainWindow.webContents.on("page-title-updated", async (e, title) => {
|
mainWindow.webContents.on("page-title-updated", (e, title) => {
|
||||||
const armCordSuffix = " - ArmCord"; /* identify */
|
const armCordSuffix = " - ArmCord"; /* identify */
|
||||||
if (!title.endsWith(armCordSuffix)) {
|
if (!title.endsWith(armCordSuffix)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
await mainWindow.webContents.executeJavaScript(
|
// REVIEW - I don't see a reason to wait for the titlebar to update
|
||||||
|
void mainWindow.webContents.executeJavaScript(
|
||||||
`document.title = '${title.replace("Discord |", "") + armCordSuffix}'`
|
`document.title = '${title.replace("Discord |", "") + armCordSuffix}'`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -193,7 +205,7 @@ async function doAfterDefiningTheWindow(): Promise<void> {
|
||||||
fs.readdirSync(themesFolder).forEach((file) => {
|
fs.readdirSync(themesFolder).forEach((file) => {
|
||||||
try {
|
try {
|
||||||
const manifest = fs.readFileSync(`${themesFolder}/${file}/manifest.json`, "utf8");
|
const manifest = fs.readFileSync(`${themesFolder}/${file}/manifest.json`, "utf8");
|
||||||
let themeFile = JSON.parse(manifest);
|
const themeFile = JSON.parse(manifest) as ThemeManifest;
|
||||||
if (
|
if (
|
||||||
fs
|
fs
|
||||||
.readFileSync(path.join(userDataPath, "/disabled.txt"))
|
.readFileSync(path.join(userDataPath, "/disabled.txt"))
|
||||||
|
@ -213,23 +225,23 @@ async function doAfterDefiningTheWindow(): Promise<void> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
await setMenu();
|
setMenu();
|
||||||
mainWindow.on("close", async (e) => {
|
mainWindow.on("close", (e) => {
|
||||||
if (process.platform === "darwin" && forceQuit) {
|
if (process.platform === "darwin" && forceQuit) {
|
||||||
mainWindow.close();
|
mainWindow.close();
|
||||||
} else {
|
} else {
|
||||||
let [width, height] = mainWindow.getSize();
|
const [width, height] = mainWindow.getSize();
|
||||||
await setWindowState({
|
setWindowState({
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
isMaximized: mainWindow.isMaximized(),
|
isMaximized: mainWindow.isMaximized(),
|
||||||
x: mainWindow.getPosition()[0],
|
x: mainWindow.getPosition()[0],
|
||||||
y: mainWindow.getPosition()[1]
|
y: mainWindow.getPosition()[1]
|
||||||
});
|
});
|
||||||
if (await getConfig("minimizeToTray")) {
|
if (getConfig("minimizeToTray")) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
mainWindow.hide();
|
mainWindow.hide();
|
||||||
} else if (!(await getConfig("minimizeToTray"))) {
|
} else if (!getConfig("minimizeToTray")) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
app.quit();
|
app.quit();
|
||||||
}
|
}
|
||||||
|
@ -244,43 +256,49 @@ async function doAfterDefiningTheWindow(): Promise<void> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// REVIEW - Awaiting javascript execution is silly
|
||||||
mainWindow.on("focus", () => {
|
mainWindow.on("focus", () => {
|
||||||
mainWindow.webContents.executeJavaScript(`document.body.removeAttribute("unFocused");`);
|
void mainWindow.webContents.executeJavaScript(`document.body.removeAttribute("unFocused");`);
|
||||||
});
|
});
|
||||||
mainWindow.on("blur", () => {
|
mainWindow.on("blur", () => {
|
||||||
mainWindow.webContents.executeJavaScript(`document.body.setAttribute("unFocused", "");`);
|
void mainWindow.webContents.executeJavaScript(`document.body.setAttribute("unFocused", "");`);
|
||||||
});
|
});
|
||||||
|
|
||||||
mainWindow.on("maximize", () => {
|
mainWindow.on("maximize", () => {
|
||||||
mainWindow.webContents.executeJavaScript(`document.body.setAttribute("isMaximized", "");`);
|
void mainWindow.webContents.executeJavaScript(`document.body.setAttribute("isMaximized", "");`);
|
||||||
});
|
});
|
||||||
mainWindow.on("unmaximize", () => {
|
mainWindow.on("unmaximize", () => {
|
||||||
mainWindow.webContents.executeJavaScript(`document.body.removeAttribute("isMaximized");`);
|
void mainWindow.webContents.executeJavaScript(`document.body.removeAttribute("isMaximized");`);
|
||||||
});
|
});
|
||||||
if ((await getConfig("inviteWebsocket")) == true) {
|
if (getConfig("inviteWebsocket")) {
|
||||||
const server = await new RPCServer();
|
// NOTE - RPCServer appears to be untyped. cool.
|
||||||
server.on("activity", (data: string) => mainWindow.webContents.send("rpc", data));
|
// REVIEW - Whatever Ducko has done here to make an async constructor is awful.
|
||||||
server.on("invite", (code: string) => {
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
||||||
console.log(code);
|
new RPCServer().then((server: EventEmitter) => {
|
||||||
createInviteWindow(code);
|
server.on("activity", (data: string) => mainWindow.webContents.send("rpc", data));
|
||||||
|
server.on("invite", (code: string) => {
|
||||||
|
console.log(code);
|
||||||
|
createInviteWindow(code);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (firstRun) {
|
if (firstRun) {
|
||||||
mainWindow.close();
|
mainWindow.close();
|
||||||
}
|
}
|
||||||
//loadURL broke for no good reason after E28
|
//loadURL broke for no good reason after E28
|
||||||
mainWindow.loadFile(`${import.meta.dirname}/../splash/redirect.html`);
|
void mainWindow.loadFile(`${import.meta.dirname}/../splash/redirect.html`);
|
||||||
|
|
||||||
if (await getConfig("skipSplash")) {
|
if (getConfig("skipSplash")) {
|
||||||
mainWindow.show();
|
mainWindow.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function createCustomWindow(): Promise<void> {
|
export function createCustomWindow(): void {
|
||||||
mainWindow = new BrowserWindow({
|
mainWindow = new BrowserWindow({
|
||||||
width: (await getWindowState("width")) ?? 835,
|
width: getWindowState("width") ?? 835,
|
||||||
height: (await getWindowState("height")) ?? 600,
|
height: getWindowState("height") ?? 600,
|
||||||
x: await getWindowState("x"),
|
x: getWindowState("x"),
|
||||||
y: await getWindowState("y"),
|
y: getWindowState("y"),
|
||||||
title: "ArmCord",
|
title: "ArmCord",
|
||||||
show: false,
|
show: false,
|
||||||
darkTheme: true,
|
darkTheme: true,
|
||||||
|
@ -292,17 +310,17 @@ export async function createCustomWindow(): Promise<void> {
|
||||||
webviewTag: true,
|
webviewTag: true,
|
||||||
sandbox: false,
|
sandbox: false,
|
||||||
preload: path.join(import.meta.dirname, "preload/preload.mjs"),
|
preload: path.join(import.meta.dirname, "preload/preload.mjs"),
|
||||||
spellcheck: await getConfig("spellcheck")
|
spellcheck: getConfig("spellcheck")
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
doAfterDefiningTheWindow();
|
doAfterDefiningTheWindow();
|
||||||
}
|
}
|
||||||
export async function createNativeWindow(): Promise<void> {
|
export function createNativeWindow(): void {
|
||||||
mainWindow = new BrowserWindow({
|
mainWindow = new BrowserWindow({
|
||||||
width: (await getWindowState("width")) ?? 835,
|
width: getWindowState("width") ?? 835,
|
||||||
height: (await getWindowState("height")) ?? 600,
|
height: getWindowState("height") ?? 600,
|
||||||
x: await getWindowState("x"),
|
x: getWindowState("x"),
|
||||||
y: await getWindowState("y"),
|
y: getWindowState("y"),
|
||||||
title: "ArmCord",
|
title: "ArmCord",
|
||||||
darkTheme: true,
|
darkTheme: true,
|
||||||
icon: iconPath,
|
icon: iconPath,
|
||||||
|
@ -314,17 +332,17 @@ export async function createNativeWindow(): Promise<void> {
|
||||||
webviewTag: true,
|
webviewTag: true,
|
||||||
sandbox: false,
|
sandbox: false,
|
||||||
preload: path.join(import.meta.dirname, "preload/preload.mjs"),
|
preload: path.join(import.meta.dirname, "preload/preload.mjs"),
|
||||||
spellcheck: await getConfig("spellcheck")
|
spellcheck: getConfig("spellcheck")
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
doAfterDefiningTheWindow();
|
doAfterDefiningTheWindow();
|
||||||
}
|
}
|
||||||
export async function createTransparentWindow(): Promise<void> {
|
export function createTransparentWindow(): void {
|
||||||
mainWindow = new BrowserWindow({
|
mainWindow = new BrowserWindow({
|
||||||
width: (await getWindowState("width")) ?? 835,
|
width: getWindowState("width") ?? 835,
|
||||||
height: (await getWindowState("height")) ?? 600,
|
height: getWindowState("height") ?? 600,
|
||||||
x: await getWindowState("x"),
|
x: getWindowState("x"),
|
||||||
y: await getWindowState("y"),
|
y: getWindowState("y"),
|
||||||
title: "ArmCord",
|
title: "ArmCord",
|
||||||
darkTheme: true,
|
darkTheme: true,
|
||||||
icon: iconPath,
|
icon: iconPath,
|
||||||
|
@ -336,12 +354,12 @@ export async function createTransparentWindow(): Promise<void> {
|
||||||
sandbox: false,
|
sandbox: false,
|
||||||
webviewTag: true,
|
webviewTag: true,
|
||||||
preload: path.join(import.meta.dirname, "preload/preload.mjs"),
|
preload: path.join(import.meta.dirname, "preload/preload.mjs"),
|
||||||
spellcheck: await getConfig("spellcheck")
|
spellcheck: getConfig("spellcheck")
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
doAfterDefiningTheWindow();
|
doAfterDefiningTheWindow();
|
||||||
}
|
}
|
||||||
export async function createInviteWindow(code: string): Promise<void> {
|
export function createInviteWindow(code: string): void {
|
||||||
inviteWindow = new BrowserWindow({
|
inviteWindow = new BrowserWindow({
|
||||||
width: 800,
|
width: 800,
|
||||||
height: 600,
|
height: 600,
|
||||||
|
@ -352,15 +370,16 @@ export async function createInviteWindow(code: string): Promise<void> {
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
sandbox: false,
|
sandbox: false,
|
||||||
spellcheck: await getConfig("spellcheck")
|
spellcheck: getConfig("spellcheck")
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let formInviteURL = `https://discord.com/invite/${code}`;
|
const formInviteURL = `https://discord.com/invite/${code}`;
|
||||||
inviteWindow.webContents.session.webRequest.onBeforeRequest((details, callback) => {
|
inviteWindow.webContents.session.webRequest.onBeforeRequest((details, callback) => {
|
||||||
if (details.url.includes("ws://")) return callback({cancel: true});
|
if (details.url.includes("ws://")) return callback({cancel: true});
|
||||||
return callback({});
|
return callback({});
|
||||||
});
|
});
|
||||||
inviteWindow.loadURL(formInviteURL);
|
// REVIEW - This shouldn't matter, since below we have an event on it
|
||||||
|
void inviteWindow.loadURL(formInviteURL);
|
||||||
inviteWindow.webContents.once("did-finish-load", () => {
|
inviteWindow.webContents.once("did-finish-load", () => {
|
||||||
if (!mainWindow.webContents.isLoading()) {
|
if (!mainWindow.webContents.isLoading()) {
|
||||||
inviteWindow.show();
|
inviteWindow.show();
|
||||||
|
|
43
src/main.ts
|
@ -11,23 +11,22 @@ import {createSplashWindow} from "./splash/main.js";
|
||||||
import {createSetupWindow} from "./setup/main.js";
|
import {createSetupWindow} from "./setup/main.js";
|
||||||
import {
|
import {
|
||||||
setConfig,
|
setConfig,
|
||||||
getConfigSync,
|
|
||||||
checkForDataFolder,
|
checkForDataFolder,
|
||||||
checkIfConfigExists,
|
checkIfConfigExists,
|
||||||
checkIfConfigIsBroken,
|
checkIfConfigIsBroken,
|
||||||
getConfig,
|
getConfig,
|
||||||
firstRun,
|
firstRun,
|
||||||
Settings,
|
|
||||||
getConfigLocation
|
getConfigLocation
|
||||||
} from "./common/config.js";
|
} from "./common/config.js";
|
||||||
import {injectElectronFlags} from "./common/flags.js";
|
import {injectElectronFlags} from "./common/flags.js";
|
||||||
import {setLang} from "./common/lang.js";
|
import {setLang} from "./common/lang.js";
|
||||||
import {installModLoader} from "./discord/extensions/mods.js";
|
import {installModLoader} from "./discord/extensions/mods.js";
|
||||||
export let iconPath: string;
|
export let iconPath: string;
|
||||||
export let settings: any;
|
import type {Settings} from "./types/settings";
|
||||||
|
export let settings: Settings;
|
||||||
export let customTitlebar: boolean;
|
export let customTitlebar: boolean;
|
||||||
|
|
||||||
app.on("render-process-gone", (event, webContents, details) => {
|
app.on("render-process-gone", (_event, _webContents, details) => {
|
||||||
if (details.reason == "crashed") {
|
if (details.reason == "crashed") {
|
||||||
app.relaunch();
|
app.relaunch();
|
||||||
}
|
}
|
||||||
|
@ -35,23 +34,23 @@ app.on("render-process-gone", (event, webContents, details) => {
|
||||||
async function args(): Promise<void> {
|
async function args(): Promise<void> {
|
||||||
let argNum = 2;
|
let argNum = 2;
|
||||||
if (process.argv[0] == "electron") argNum++;
|
if (process.argv[0] == "electron") argNum++;
|
||||||
let args = process.argv[argNum];
|
const args = process.argv[argNum];
|
||||||
if (args == undefined) return;
|
if (args == undefined) return;
|
||||||
if (args.startsWith("--")) return; //electron flag
|
if (args.startsWith("--")) return; //electron flag
|
||||||
if (args.includes("=")) {
|
if (args.includes("=")) {
|
||||||
let e = args.split("=");
|
const e = args.split("=");
|
||||||
await setConfig(e[0] as keyof Settings, e[1]);
|
setConfig(e[0] as keyof Settings, e[1]);
|
||||||
console.log(`Setting ${e[0]} to ${e[1]}`);
|
console.log(`Setting ${e[0]} to ${e[1]}`);
|
||||||
app.relaunch();
|
app.relaunch();
|
||||||
app.exit();
|
app.exit();
|
||||||
} else if (args == "themes") {
|
} else if (args == "themes") {
|
||||||
app.whenReady().then(async () => {
|
await app.whenReady().then(async () => {
|
||||||
createTManagerWindow();
|
await createTManagerWindow();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
args(); // i want my top level awaits
|
await args(); // i want my top level awaits - IMPLEMENTED :)
|
||||||
if (!app.requestSingleInstanceLock() && getConfigSync("multiInstance") == (false ?? undefined)) {
|
if (!app.requestSingleInstanceLock() && getConfig("multiInstance") == (false ?? undefined)) {
|
||||||
// if value isn't set after 3.2.4
|
// if value isn't set after 3.2.4
|
||||||
// kill if 2nd instance
|
// kill if 2nd instance
|
||||||
app.quit();
|
app.quit();
|
||||||
|
@ -82,21 +81,22 @@ if (!app.requestSingleInstanceLock() && getConfigSync("multiInstance") == (false
|
||||||
checkIfConfigIsBroken();
|
checkIfConfigIsBroken();
|
||||||
injectElectronFlags();
|
injectElectronFlags();
|
||||||
console.log("[Config Manager] Current config: " + fs.readFileSync(getConfigLocation(), "utf-8"));
|
console.log("[Config Manager] Current config: " + fs.readFileSync(getConfigLocation(), "utf-8"));
|
||||||
app.whenReady().then(async () => {
|
void app.whenReady().then(async () => {
|
||||||
if ((await getConfig("customIcon")) !== undefined ?? null) {
|
// REVIEW - Awaiting the line above will cause a hang at startup
|
||||||
iconPath = await getConfig("customIcon");
|
if (getConfig("customIcon") !== null) {
|
||||||
|
iconPath = getConfig("customIcon");
|
||||||
} else {
|
} else {
|
||||||
iconPath = path.join(import.meta.dirname, "../", "/assets/desktop.png");
|
iconPath = path.join(import.meta.dirname, "../", "/assets/desktop.png");
|
||||||
}
|
}
|
||||||
async function init(): Promise<void> {
|
async function init(): Promise<void> {
|
||||||
if ((await getConfig("skipSplash")) == false) {
|
if (getConfig("skipSplash") == false) {
|
||||||
createSplashWindow();
|
void createSplashWindow(); // REVIEW - Awaiting will hang at start
|
||||||
}
|
}
|
||||||
if (firstRun == true) {
|
if (firstRun == true) {
|
||||||
await setLang(new Intl.DateTimeFormat().resolvedOptions().locale);
|
setLang(new Intl.DateTimeFormat().resolvedOptions().locale);
|
||||||
createSetupWindow();
|
await createSetupWindow();
|
||||||
}
|
}
|
||||||
switch (await getConfig("windowStyle")) {
|
switch (getConfig("windowStyle")) {
|
||||||
case "default":
|
case "default":
|
||||||
createCustomWindow();
|
createCustomWindow();
|
||||||
customTitlebar = true;
|
customTitlebar = true;
|
||||||
|
@ -125,8 +125,9 @@ if (!app.requestSingleInstanceLock() && getConfigSync("multiInstance") == (false
|
||||||
callback(true);
|
callback(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
app.on("activate", async function () {
|
app.on("activate", function () {
|
||||||
if (BrowserWindow.getAllWindows().length === 0) await init();
|
// REVIEW - I don't think it really matters if this promise is voided
|
||||||
|
if (BrowserWindow.getAllWindows().length === 0) void init();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,11 @@ import {BrowserWindow, app, shell} from "electron";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import {getDisplayVersion} from "../common/version.js";
|
import {getDisplayVersion} from "../common/version.js";
|
||||||
|
import type {ThemeManifest} from "../types/themeManifest.d.js";
|
||||||
let settingsWindow: BrowserWindow;
|
let settingsWindow: BrowserWindow;
|
||||||
let instance = 0;
|
let instance = 0;
|
||||||
|
|
||||||
export function createSettingsWindow(): void {
|
export async function createSettingsWindow(): Promise<void> {
|
||||||
console.log("Creating a settings window.");
|
console.log("Creating a settings window.");
|
||||||
instance += 1;
|
instance += 1;
|
||||||
if (instance > 1) {
|
if (instance > 1) {
|
||||||
|
@ -28,7 +29,7 @@ export function createSettingsWindow(): void {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
async function settingsLoadPage(): Promise<void> {
|
async function settingsLoadPage(): Promise<void> {
|
||||||
settingsWindow.loadURL(`file://${import.meta.dirname}/settings.html`);
|
await settingsWindow.loadURL(`file://${import.meta.dirname}/settings.html`);
|
||||||
}
|
}
|
||||||
const userDataPath = app.getPath("userData");
|
const userDataPath = app.getPath("userData");
|
||||||
const themesFolder = `${userDataPath}/themes/`;
|
const themesFolder = `${userDataPath}/themes/`;
|
||||||
|
@ -43,7 +44,7 @@ export function createSettingsWindow(): void {
|
||||||
fs.readdirSync(themesFolder).forEach((file) => {
|
fs.readdirSync(themesFolder).forEach((file) => {
|
||||||
try {
|
try {
|
||||||
const manifest = fs.readFileSync(`${themesFolder}/${file}/manifest.json`, "utf8");
|
const manifest = fs.readFileSync(`${themesFolder}/${file}/manifest.json`, "utf8");
|
||||||
let themeFile = JSON.parse(manifest);
|
const themeFile = JSON.parse(manifest) as ThemeManifest;
|
||||||
if (
|
if (
|
||||||
fs
|
fs
|
||||||
.readFileSync(path.join(userDataPath, "/disabled.txt"))
|
.readFileSync(path.join(userDataPath, "/disabled.txt"))
|
||||||
|
@ -64,10 +65,10 @@ export function createSettingsWindow(): void {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
settingsWindow.webContents.setWindowOpenHandler(({url}) => {
|
settingsWindow.webContents.setWindowOpenHandler(({url}) => {
|
||||||
shell.openExternal(url);
|
void shell.openExternal(url);
|
||||||
return {action: "deny"};
|
return {action: "deny"};
|
||||||
});
|
});
|
||||||
settingsLoadPage();
|
await settingsLoadPage();
|
||||||
settingsWindow.on("close", () => {
|
settingsWindow.on("close", () => {
|
||||||
instance = 0;
|
instance = 0;
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
import {contextBridge, ipcRenderer} from "electron";
|
import {contextBridge, ipcRenderer} from "electron";
|
||||||
|
import {Settings} from "../types/settings";
|
||||||
//import {addStyle} from "../utils.js";
|
//import {addStyle} from "../utils.js";
|
||||||
console.log("ArmCord Settings");
|
console.log("ArmCord Settings");
|
||||||
console.log(process.platform);
|
console.log(process.platform);
|
||||||
contextBridge.exposeInMainWorld("settings", {
|
contextBridge.exposeInMainWorld("settings", {
|
||||||
save: (...args: any) => ipcRenderer.send("saveSettings", ...args),
|
// REVIEW - this may be typed incorrectly, I'm not sure how "..." works
|
||||||
|
save: (...args: Settings[]) => ipcRenderer.send("saveSettings", ...args),
|
||||||
restart: () => ipcRenderer.send("restart"),
|
restart: () => ipcRenderer.send("restart"),
|
||||||
saveAlert: (restartFunc: any) => ipcRenderer.send("saveAlert", restartFunc),
|
// REVIEW - I couldn't find a reference to anything about the below function
|
||||||
|
saveAlert: (restartFunc: () => void) => ipcRenderer.send("saveAlert", restartFunc),
|
||||||
getLang: (toGet: string) => ipcRenderer.invoke("getLang", toGet),
|
getLang: (toGet: string) => ipcRenderer.invoke("getLang", toGet),
|
||||||
get: (toGet: string) => ipcRenderer.invoke("getSetting", toGet),
|
get: (toGet: string) => ipcRenderer.invoke("getSetting", toGet),
|
||||||
openThemesFolder: () => ipcRenderer.send("openThemesFolder"),
|
openThemesFolder: () => ipcRenderer.send("openThemesFolder"),
|
||||||
|
@ -17,7 +20,8 @@ contextBridge.exposeInMainWorld("settings", {
|
||||||
crash: () => ipcRenderer.send("crash"),
|
crash: () => ipcRenderer.send("crash"),
|
||||||
os: process.platform
|
os: process.platform
|
||||||
});
|
});
|
||||||
|
/*
|
||||||
ipcRenderer.on("themeLoader", (_event, message) => {
|
ipcRenderer.on("themeLoader", (_event, message) => {
|
||||||
//addStyle(message);
|
//addStyle(message);
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
|
|
@ -260,6 +260,7 @@ select option {
|
||||||
select {
|
select {
|
||||||
-webkit-appearance: button;
|
-webkit-appearance: button;
|
||||||
-moz-appearance: button;
|
-moz-appearance: button;
|
||||||
|
appearance: button;
|
||||||
background-color: var(--background-secondary-alt);
|
background-color: var(--background-secondary-alt);
|
||||||
background-position: center right;
|
background-position: center right;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
|
|
@ -1,42 +1,47 @@
|
||||||
import {BrowserWindow, app, ipcMain} from "electron";
|
import {BrowserWindow, app, ipcMain} from "electron";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import * as fs from "fs";
|
import fs from "fs";
|
||||||
import {iconPath} from "../main.js";
|
import {iconPath} from "../main.js";
|
||||||
import {setConfigBulk, getConfigLocation, Settings} from "../common/config.js";
|
import {setConfigBulk, getConfigLocation} from "../common/config.js";
|
||||||
|
import type {Settings} from "../types/settings.d.js";
|
||||||
|
|
||||||
let setupWindow: BrowserWindow;
|
let setupWindow: BrowserWindow;
|
||||||
export function createSetupWindow(): void {
|
export async function createSetupWindow(): Promise<void> {
|
||||||
setupWindow = new BrowserWindow({
|
// NOTE - intentionally hang the process until setup is completed
|
||||||
width: 390,
|
return new Promise((resolve) => {
|
||||||
height: 470,
|
setupWindow = new BrowserWindow({
|
||||||
title: "ArmCord Setup",
|
width: 390,
|
||||||
darkTheme: true,
|
height: 470,
|
||||||
icon: iconPath,
|
title: "ArmCord Setup",
|
||||||
frame: false,
|
darkTheme: true,
|
||||||
autoHideMenuBar: true,
|
icon: iconPath,
|
||||||
webPreferences: {
|
frame: false,
|
||||||
sandbox: false,
|
autoHideMenuBar: true,
|
||||||
spellcheck: false,
|
webPreferences: {
|
||||||
preload: path.join(import.meta.dirname, "preload.mjs")
|
sandbox: false,
|
||||||
}
|
spellcheck: false,
|
||||||
});
|
preload: path.join(import.meta.dirname, "preload.mjs")
|
||||||
ipcMain.on("saveSettings", (_event, args: Settings) => {
|
}
|
||||||
console.log(args);
|
|
||||||
setConfigBulk(args);
|
|
||||||
});
|
|
||||||
ipcMain.on("setup-minimize", () => {
|
|
||||||
setupWindow.minimize();
|
|
||||||
});
|
|
||||||
ipcMain.on("setup-getOS", (event) => {
|
|
||||||
event.returnValue = process.platform;
|
|
||||||
});
|
|
||||||
ipcMain.on("setup-quit", async () => {
|
|
||||||
fs.unlink(await getConfigLocation(), (err) => {
|
|
||||||
if (err) throw err;
|
|
||||||
|
|
||||||
console.log('Closed during setup. "settings.json" was deleted');
|
|
||||||
app.quit();
|
|
||||||
});
|
});
|
||||||
|
ipcMain.on("saveSettings", (_event, args: Settings) => {
|
||||||
|
console.log(args);
|
||||||
|
setConfigBulk(args);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
ipcMain.on("setup-minimize", () => {
|
||||||
|
setupWindow.minimize();
|
||||||
|
});
|
||||||
|
ipcMain.on("setup-getOS", (event) => {
|
||||||
|
event.returnValue = process.platform;
|
||||||
|
});
|
||||||
|
ipcMain.on("setup-quit", () => {
|
||||||
|
fs.unlink(getConfigLocation(), (err) => {
|
||||||
|
if (err) throw err;
|
||||||
|
|
||||||
|
console.log('Closed during setup. "settings.json" was deleted');
|
||||||
|
app.quit();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
void setupWindow.loadURL(`file://${import.meta.dirname}/setup.html`);
|
||||||
});
|
});
|
||||||
setupWindow.loadURL(`file://${import.meta.dirname}/setup.html`);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import {contextBridge, ipcRenderer} from "electron";
|
import {contextBridge, ipcRenderer} from "electron";
|
||||||
import {injectTitlebar} from "../discord/preload/titlebar.mjs";
|
import {injectTitlebar} from "../discord/preload/titlebar.mjs";
|
||||||
|
import {Settings} from "../types/settings";
|
||||||
|
|
||||||
injectTitlebar();
|
injectTitlebar();
|
||||||
contextBridge.exposeInMainWorld("armcordinternal", {
|
contextBridge.exposeInMainWorld("armcordinternal", {
|
||||||
restart: () => ipcRenderer.send("restart"),
|
restart: () => ipcRenderer.send("restart"),
|
||||||
getOS: ipcRenderer.sendSync("setup-getOS"),
|
getOS: ipcRenderer.sendSync("setup-getOS") as string, // String as far as I care.
|
||||||
saveSettings: (...args: any) => ipcRenderer.send("saveSettings", ...args),
|
saveSettings: (...args: [Settings]) => ipcRenderer.send("saveSettings", ...args),
|
||||||
getLang: (toGet: string) =>
|
getLang: (toGet: string) =>
|
||||||
ipcRenderer.invoke("getLang", toGet).then((result) => {
|
ipcRenderer.invoke("getLang", toGet).then((result: string) => {
|
||||||
return result;
|
return result;
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
@ -123,9 +123,8 @@
|
||||||
});
|
});
|
||||||
if (window.armcordinternal.getOS == "linux") {
|
if (window.armcordinternal.getOS == "linux") {
|
||||||
document.getElementById("tray").value = "false";
|
document.getElementById("tray").value = "false";
|
||||||
document.getElementById(
|
document.getElementById("linuxNotice").innerHTML =
|
||||||
"linuxNotice"
|
`Linux may not work well with tray icons. Depending on your system configuration, you may not be able to see the tray icon. Enable at your own risk. Can be changed later.`;
|
||||||
).innerHTML = `Linux may not work well with tray icons. Depending on your system configuration, you may not be able to see the tray icon. Enable at your own risk. Can be changed later.`;
|
|
||||||
}
|
}
|
||||||
document.getElementById("next-page4").addEventListener("click", () => {
|
document.getElementById("next-page4").addEventListener("click", () => {
|
||||||
window.armcordinternal.saveSettings({
|
window.armcordinternal.saveSettings({
|
||||||
|
|
|
@ -19,5 +19,5 @@ export async function createSplashWindow(): Promise<void> {
|
||||||
preload: path.join(import.meta.dirname, "preload.mjs")
|
preload: path.join(import.meta.dirname, "preload.mjs")
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
splashWindow.loadFile(path.join(import.meta.dirname, "splash.html"));
|
await splashWindow.loadFile(path.join(import.meta.dirname, "splash.html"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,10 @@ import {contextBridge, ipcRenderer} from "electron";
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld("internal", {
|
contextBridge.exposeInMainWorld("internal", {
|
||||||
restart: () => ipcRenderer.send("restart"),
|
restart: () => ipcRenderer.send("restart"),
|
||||||
installState: ipcRenderer.sendSync("modInstallState"),
|
installState: ipcRenderer.sendSync("modInstallState") as string,
|
||||||
version: ipcRenderer.sendSync("get-app-version", "app-version"),
|
version: ipcRenderer.sendSync("get-app-version", "app-version") as string,
|
||||||
getLang: (toGet: string) =>
|
getLang: (toGet: string) =>
|
||||||
ipcRenderer.invoke("getLang", toGet).then((result) => {
|
ipcRenderer.invoke("getLang", toGet).then((result: string) => {
|
||||||
return result;
|
return result;
|
||||||
}),
|
}),
|
||||||
splashEnd: () => ipcRenderer.send("splashEnd")
|
splashEnd: () => ipcRenderer.send("splashEnd")
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Loading</title>
|
<title>Loading</title>
|
||||||
|
|
|
@ -53,6 +53,7 @@ body {
|
||||||
font-family: "Whitney", sans-serif;
|
font-family: "Whitney", sans-serif;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
|
|
@ -1,36 +1,22 @@
|
||||||
import {BrowserWindow, app, dialog, ipcMain, shell} from "electron";
|
import {BrowserWindow, app, dialog, ipcMain, shell} from "electron";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import {sleep} from "../common/sleep.js";
|
|
||||||
import {createInviteWindow, mainWindow} from "../discord/window.js";
|
import {createInviteWindow, mainWindow} from "../discord/window.js";
|
||||||
|
import type {ThemeManifest} from "../types/themeManifest.d.js";
|
||||||
let themeWindow: BrowserWindow;
|
let themeWindow: BrowserWindow;
|
||||||
let instance = 0;
|
let instance = 0;
|
||||||
interface ThemeManifest {
|
|
||||||
name?: string;
|
|
||||||
author?: string;
|
|
||||||
description?: string;
|
|
||||||
version?: string;
|
|
||||||
invite?: string;
|
|
||||||
authorId?: string;
|
|
||||||
theme: string;
|
|
||||||
authorLink?: string;
|
|
||||||
donate?: string;
|
|
||||||
patreon?: string;
|
|
||||||
website?: string;
|
|
||||||
source?: string;
|
|
||||||
updateSrc?: string;
|
|
||||||
supportsArmCordTitlebar?: boolean;
|
|
||||||
}
|
|
||||||
function parseBDManifest(content: string) {
|
function parseBDManifest(content: string) {
|
||||||
const metaReg = /@([^ ]*) (.*)/g;
|
const metaReg = /@([^ ]*) (.*)/g;
|
||||||
if (!content.startsWith("/**")) {
|
if (!content.startsWith("/**")) {
|
||||||
throw new Error("Not a manifest.");
|
throw new Error("Not a manifest.");
|
||||||
}
|
}
|
||||||
let manifest: ThemeManifest = {theme: "src.css"};
|
const manifest: ThemeManifest = {theme: "src.css", name: "null"}; // Will be defined later
|
||||||
|
|
||||||
let match;
|
let match;
|
||||||
while ((match = metaReg.exec(content)) !== null) {
|
while ((match = metaReg.exec(content)) !== null) {
|
||||||
let [_, key, value] = match;
|
const [_, key] = match;
|
||||||
|
let [value] = match;
|
||||||
if (key === "import") break;
|
if (key === "import") break;
|
||||||
|
|
||||||
value = value.trim();
|
value = value.trim();
|
||||||
|
@ -88,7 +74,7 @@ function parseBDManifest(content: string) {
|
||||||
}
|
}
|
||||||
const userDataPath = app.getPath("userData");
|
const userDataPath = app.getPath("userData");
|
||||||
const themesPath = path.join(userDataPath, "/themes/");
|
const themesPath = path.join(userDataPath, "/themes/");
|
||||||
export function createTManagerWindow(): void {
|
export async function createTManagerWindow(): Promise<void> {
|
||||||
console.log("Creating theme manager window.");
|
console.log("Creating theme manager window.");
|
||||||
instance += 1;
|
instance += 1;
|
||||||
if (instance > 1) {
|
if (instance > 1) {
|
||||||
|
@ -118,13 +104,13 @@ export function createTManagerWindow(): void {
|
||||||
if (url.startsWith("https://discord.gg/")) {
|
if (url.startsWith("https://discord.gg/")) {
|
||||||
createInviteWindow(url.replace("https://discord.gg/", ""));
|
createInviteWindow(url.replace("https://discord.gg/", ""));
|
||||||
} else {
|
} else {
|
||||||
shell.openExternal(url);
|
void shell.openExternal(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
async function managerLoadPage(): Promise<void> {
|
async function managerLoadPage(): Promise<void> {
|
||||||
themeWindow.loadFile(`${import.meta.dirname}/manager.html`);
|
await themeWindow.loadFile(`${import.meta.dirname}/manager.html`);
|
||||||
}
|
}
|
||||||
const userDataPath = app.getPath("userData");
|
const userDataPath = app.getPath("userData");
|
||||||
const themesFolder = `${userDataPath}/themes/`;
|
const themesFolder = `${userDataPath}/themes/`;
|
||||||
|
@ -135,27 +121,24 @@ export function createTManagerWindow(): void {
|
||||||
if (!fs.existsSync(`${userDataPath}/disabled.txt`)) {
|
if (!fs.existsSync(`${userDataPath}/disabled.txt`)) {
|
||||||
fs.writeFileSync(path.join(userDataPath, "/disabled.txt"), "");
|
fs.writeFileSync(path.join(userDataPath, "/disabled.txt"), "");
|
||||||
}
|
}
|
||||||
ipcMain.on("openThemesFolder", async () => {
|
ipcMain.on("openThemesFolder", () => {
|
||||||
shell.showItemInFolder(themesPath);
|
shell.showItemInFolder(themesPath);
|
||||||
await sleep(1000);
|
|
||||||
});
|
});
|
||||||
ipcMain.on("reloadMain", async () => {
|
ipcMain.on("reloadMain", () => {
|
||||||
mainWindow.webContents.reload();
|
mainWindow.webContents.reload();
|
||||||
});
|
});
|
||||||
ipcMain.on("addToDisabled", async (_event, name: string) => {
|
ipcMain.on("addToDisabled", (_event, name: string) => {
|
||||||
fs.appendFileSync(path.join(userDataPath, "/disabled.txt"), `${name}\n`);
|
fs.appendFileSync(path.join(userDataPath, "/disabled.txt"), `${name}\n`);
|
||||||
sleep(1000);
|
|
||||||
});
|
});
|
||||||
ipcMain.on("disabled", async (e) => {
|
ipcMain.on("disabled", (e) => {
|
||||||
e.returnValue = fs.readFileSync(path.join(userDataPath, "/disabled.txt")).toString();
|
e.returnValue = fs.readFileSync(path.join(userDataPath, "/disabled.txt")).toString();
|
||||||
});
|
});
|
||||||
ipcMain.on("removeFromDisabled", async (_event, name: string) => {
|
ipcMain.on("removeFromDisabled", (_event, name: string) => {
|
||||||
let e = await fs.readFileSync(path.join(userDataPath, "/disabled.txt")).toString();
|
const e = fs.readFileSync(path.join(userDataPath, "/disabled.txt")).toString();
|
||||||
fs.writeFileSync(path.join(userDataPath, "/disabled.txt"), e.replace(name, ""));
|
fs.writeFileSync(path.join(userDataPath, "/disabled.txt"), e.replace(name, ""));
|
||||||
sleep(1000);
|
|
||||||
});
|
});
|
||||||
ipcMain.on("uninstallTheme", async (_event, id: string) => {
|
ipcMain.on("uninstallTheme", (_event, id: string) => {
|
||||||
let themePath = path.join(themesFolder, id);
|
const themePath = path.join(themesFolder, id);
|
||||||
if (fs.existsSync(themePath)) {
|
if (fs.existsSync(themePath)) {
|
||||||
fs.rmdirSync(themePath, {recursive: true});
|
fs.rmdirSync(themePath, {recursive: true});
|
||||||
console.log(`Removed ${id} folder`);
|
console.log(`Removed ${id} folder`);
|
||||||
|
@ -166,33 +149,35 @@ export function createTManagerWindow(): void {
|
||||||
themeWindow.webContents.reload();
|
themeWindow.webContents.reload();
|
||||||
mainWindow.webContents.reload();
|
mainWindow.webContents.reload();
|
||||||
});
|
});
|
||||||
ipcMain.on("installBDTheme", async (_event, link: string) => {
|
ipcMain.on("installBDTheme", (_event, link: string) => {
|
||||||
try {
|
return async () => {
|
||||||
let code = await (await fetch(link)).text();
|
try {
|
||||||
let manifest = parseBDManifest(code);
|
const code = await (await fetch(link)).text();
|
||||||
let themePath = path.join(themesFolder, `${manifest.name?.replace(" ", "-")}-BD`);
|
const manifest = parseBDManifest(code);
|
||||||
if (!fs.existsSync(themePath)) {
|
const themePath = path.join(themesFolder, `${manifest.name?.replace(" ", "-")}-BD`);
|
||||||
fs.mkdirSync(themePath);
|
if (!fs.existsSync(themePath)) {
|
||||||
console.log(`Created ${manifest.name} folder`);
|
fs.mkdirSync(themePath);
|
||||||
|
console.log(`Created ${manifest.name} folder`);
|
||||||
|
}
|
||||||
|
manifest.updateSrc = link;
|
||||||
|
if (code.includes(".titlebar")) manifest.supportsArmCordTitlebar = true;
|
||||||
|
else manifest.supportsArmCordTitlebar = false;
|
||||||
|
fs.writeFileSync(path.join(themePath, "manifest.json"), JSON.stringify(manifest));
|
||||||
|
fs.writeFileSync(path.join(themePath, "src.css"), code);
|
||||||
|
dialog.showMessageBoxSync({
|
||||||
|
type: "info",
|
||||||
|
title: "BD Theme import success",
|
||||||
|
message: "Successfully imported theme from link."
|
||||||
|
});
|
||||||
|
themeWindow.webContents.reload();
|
||||||
|
mainWindow.webContents.reload();
|
||||||
|
} catch (e) {
|
||||||
|
dialog.showErrorBox(
|
||||||
|
"BD Theme import fail",
|
||||||
|
"Failed to import theme from link. Please make sure that it's a valid BetterDiscord Theme."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
manifest.updateSrc = link;
|
};
|
||||||
if (code.includes(".titlebar")) manifest.supportsArmCordTitlebar = true;
|
|
||||||
else manifest.supportsArmCordTitlebar = false;
|
|
||||||
fs.writeFileSync(path.join(themePath, "manifest.json"), JSON.stringify(manifest));
|
|
||||||
fs.writeFileSync(path.join(themePath, "src.css"), code);
|
|
||||||
dialog.showMessageBoxSync({
|
|
||||||
type: "info",
|
|
||||||
title: "BD Theme import success",
|
|
||||||
message: "Successfully imported theme from link."
|
|
||||||
});
|
|
||||||
themeWindow.webContents.reload();
|
|
||||||
mainWindow.webContents.reload();
|
|
||||||
} catch (e) {
|
|
||||||
dialog.showErrorBox(
|
|
||||||
"BD Theme import fail",
|
|
||||||
"Failed to import theme from link. Please make sure that it's a valid BetterDiscord Theme."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
themeWindow.webContents.on("did-finish-load", () => {
|
themeWindow.webContents.on("did-finish-load", () => {
|
||||||
fs.readdirSync(themesFolder).forEach((file) => {
|
fs.readdirSync(themesFolder).forEach((file) => {
|
||||||
|
@ -206,7 +191,7 @@ export function createTManagerWindow(): void {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
managerLoadPage();
|
await managerLoadPage();
|
||||||
themeWindow.on("close", () => {
|
themeWindow.on("close", () => {
|
||||||
instance = 0;
|
instance = 0;
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<style>
|
<style>
|
||||||
|
@ -213,7 +213,9 @@
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
box-shadow:
|
||||||
|
0 4px 8px 0 rgba(0, 0, 0, 0.2),
|
||||||
|
0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
||||||
-webkit-animation-name: animatetop;
|
-webkit-animation-name: animatetop;
|
||||||
-webkit-animation-duration: 0.4s;
|
-webkit-animation-duration: 0.4s;
|
||||||
animation-name: animatetop;
|
animation-name: animatetop;
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
import {ipcRenderer, contextBridge} from "electron";
|
import {ipcRenderer, contextBridge} from "electron";
|
||||||
import {sleep} from "../common/sleep.js";
|
import {sleep} from "../common/sleep.js";
|
||||||
|
import {ThemeManifest} from "../types/themeManifest";
|
||||||
contextBridge.exposeInMainWorld("themes", {
|
contextBridge.exposeInMainWorld("themes", {
|
||||||
install: (url: string) => ipcRenderer.send("installBDTheme", url),
|
install: (url: string) => ipcRenderer.send("installBDTheme", url),
|
||||||
uninstall: (id: string) => ipcRenderer.send("uninstallTheme", id)
|
uninstall: (id: string) => ipcRenderer.send("uninstallTheme", id)
|
||||||
});
|
});
|
||||||
ipcRenderer.on("themeManifest", (_event, json) => {
|
ipcRenderer.on("themeManifest", (_event, json: string) => {
|
||||||
let manifest = JSON.parse(json);
|
async () => {
|
||||||
console.log(manifest);
|
const manifest = JSON.parse(json) as ThemeManifest;
|
||||||
sleep(1000);
|
console.log(manifest);
|
||||||
let e = document.getElementById("cardBox");
|
await sleep(1000); // REVIEW - This is all that requires async, would be nice if it could be removed.
|
||||||
let id = manifest.name.replace(" ", "-");
|
const e = document.getElementById("cardBox");
|
||||||
e?.insertAdjacentHTML(
|
const id = manifest.name.replace(" ", "-");
|
||||||
"beforeend",
|
e?.insertAdjacentHTML(
|
||||||
`
|
"beforeend",
|
||||||
|
`
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="flex-box">
|
<div class="flex-box">
|
||||||
<h3 id="${`${id}header`}">${manifest.name}</h3>
|
<h3 id="${`${id}header`}">${manifest.name}</h3>
|
||||||
|
@ -22,50 +24,47 @@ ipcRenderer.on("themeManifest", (_event, json) => {
|
||||||
<p>${manifest.description}</p>
|
<p>${manifest.description}</p>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
);
|
);
|
||||||
document.getElementById(`${id}header`)!.addEventListener("click", () => {
|
document.getElementById(`${id}header`)!.addEventListener("click", () => {
|
||||||
document.getElementById("themeInfoModal")!.style.display = "block";
|
document.getElementById("themeInfoModal")!.style.display = "block";
|
||||||
document.getElementById("themeInfoName")!.textContent = `${manifest.name} by ${manifest.author}`;
|
document.getElementById("themeInfoName")!.textContent = `${manifest.name} by ${manifest.author}`;
|
||||||
document.getElementById("themeInfoDesc")!.textContent = `${manifest.description}\n\n${manifest.version}`;
|
document.getElementById("themeInfoDesc")!.textContent = `${manifest.description}\n\n${manifest.version}`;
|
||||||
if (manifest.supportsArmCordTitlebar !== undefined) {
|
if (manifest.supportsArmCordTitlebar !== undefined) {
|
||||||
document.getElementById(
|
document.getElementById("themeInfoButtons")!.innerHTML +=
|
||||||
"themeInfoButtons"
|
`<img class="themeInfoIcon" id="removeTheme" onclick="themes.uninstall('${id}')" title="Remove the theme" src="https://raw.githubusercontent.com/ArmCord/BrandingStuff/main/Trash.png"></img>
|
||||||
)!.innerHTML += `<img class="themeInfoIcon" id="removeTheme" onclick="themes.uninstall('${id}')" title="Remove the theme" src="https://raw.githubusercontent.com/ArmCord/BrandingStuff/main/Trash.png"></img>
|
|
||||||
<img class="themeInfoIcon" id="updateTheme" onclick="themes.install('${manifest.updateSrc}')" title="Update your theme" src="https://raw.githubusercontent.com/ArmCord/BrandingStuff/main/UpgradeArrow.png"></img>
|
<img class="themeInfoIcon" id="updateTheme" onclick="themes.install('${manifest.updateSrc}')" title="Update your theme" src="https://raw.githubusercontent.com/ArmCord/BrandingStuff/main/UpgradeArrow.png"></img>
|
||||||
<img class="themeInfoIcon" id="compatibility" title="Supports ArmCord Titlebar" src=""></img>`;
|
<img class="themeInfoIcon" id="compatibility" title="Supports ArmCord Titlebar" src=""></img>`;
|
||||||
console.log("e");
|
console.log("e");
|
||||||
if (manifest.supportsArmCordTitlebar == true) {
|
if (manifest.supportsArmCordTitlebar == true) {
|
||||||
(document.getElementById(`compatibility`) as HTMLImageElement).src =
|
(document.getElementById(`compatibility`) as HTMLImageElement).src =
|
||||||
"https://raw.githubusercontent.com/ArmCord/BrandingStuff/main/Window.png";
|
"https://raw.githubusercontent.com/ArmCord/BrandingStuff/main/Window.png";
|
||||||
} else {
|
} else {
|
||||||
(document.getElementById(`compatibility`) as HTMLImageElement).src =
|
(document.getElementById(`compatibility`) as HTMLImageElement).src =
|
||||||
"https://raw.githubusercontent.com/ArmCord/BrandingStuff/main/WindowUnsupported.png";
|
"https://raw.githubusercontent.com/ArmCord/BrandingStuff/main/WindowUnsupported.png";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (manifest.source != undefined)
|
||||||
|
document.getElementById("themeInfoButtons")!.innerHTML +=
|
||||||
|
`<a href="${manifest.source}" class="button">Source code</a>`;
|
||||||
|
if (manifest.website != undefined)
|
||||||
|
document.getElementById("themeInfoButtons")!.innerHTML +=
|
||||||
|
`<a href="${manifest.website}" class="button">Website</a>`;
|
||||||
|
if (manifest.invite != undefined)
|
||||||
|
document.getElementById("themeInfoButtons")!.innerHTML +=
|
||||||
|
`<a href="${`https://discord.gg/${manifest.invite}`}" class="button">Support Discord</a>`;
|
||||||
|
});
|
||||||
|
if (!(ipcRenderer.sendSync("disabled") as string[]).includes(id)) {
|
||||||
|
(document.getElementById(id) as HTMLInputElement).checked = true;
|
||||||
}
|
}
|
||||||
if (manifest.source != undefined)
|
(document.getElementById(id) as HTMLInputElement).addEventListener("input", function () {
|
||||||
document.getElementById(
|
ipcRenderer.send("reloadMain");
|
||||||
"themeInfoButtons"
|
if (this.checked) {
|
||||||
)!.innerHTML += `<a href="${manifest.source}" class="button">Source code</a>`;
|
ipcRenderer.send("removeFromDisabled", id);
|
||||||
if (manifest.website != undefined)
|
} else {
|
||||||
document.getElementById(
|
ipcRenderer.send("addToDisabled", id);
|
||||||
"themeInfoButtons"
|
}
|
||||||
)!.innerHTML += `<a href="${manifest.website}" class="button">Website</a>`;
|
});
|
||||||
if (manifest.invite != undefined)
|
};
|
||||||
document.getElementById(
|
|
||||||
"themeInfoButtons"
|
|
||||||
)!.innerHTML += `<a href="${`https://discord.gg/${manifest.invite}`}" class="button">Support Discord</a>`;
|
|
||||||
});
|
|
||||||
if (!ipcRenderer.sendSync("disabled").includes(id)) {
|
|
||||||
(document.getElementById(id) as HTMLInputElement).checked = true;
|
|
||||||
}
|
|
||||||
(document.getElementById(id) as HTMLInputElement)!.addEventListener("input", function (evt) {
|
|
||||||
ipcRenderer.send("reloadMain");
|
|
||||||
if (this.checked) {
|
|
||||||
ipcRenderer.send("removeFromDisabled", id);
|
|
||||||
} else {
|
|
||||||
ipcRenderer.send("addToDisabled", id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
document.getElementsByClassName("close")[0].addEventListener("click", () => {
|
document.getElementsByClassName("close")[0].addEventListener("click", () => {
|
||||||
|
@ -73,6 +72,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||||
document.getElementById("themeInfoButtons")!.innerHTML = "";
|
document.getElementById("themeInfoButtons")!.innerHTML = "";
|
||||||
});
|
});
|
||||||
document.getElementById("download")!.addEventListener("click", () => {
|
document.getElementById("download")!.addEventListener("click", () => {
|
||||||
ipcRenderer.send("installBDTheme", (document.getElementById("themeLink") as HTMLInputElement)!.value);
|
ipcRenderer.send("installBDTheme", (document.getElementById("themeLink") as HTMLInputElement).value);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
38
src/tray.ts
|
@ -1,20 +1,20 @@
|
||||||
import * as fs from "fs";
|
import fs from "fs";
|
||||||
import {Menu, MessageBoxOptions, Tray, app, dialog, nativeImage} from "electron";
|
import {Menu, MessageBoxOptions, Tray, app, dialog, nativeImage} from "electron";
|
||||||
import {createInviteWindow, mainWindow} from "./discord/window.js";
|
import {createInviteWindow, mainWindow} from "./discord/window.js";
|
||||||
import * as path from "path";
|
import path from "path";
|
||||||
import {createSettingsWindow} from "./settings/main.js";
|
import {createSettingsWindow} from "./settings/main.js";
|
||||||
import {getConfig, getConfigLocation, setConfig} from "./common/config.js";
|
import {getConfig, getConfigLocation, setConfig} from "./common/config.js";
|
||||||
import {getDisplayVersion} from "./common/version.js";
|
import {getDisplayVersion} from "./common/version.js";
|
||||||
export let tray: any = null;
|
export let tray: Tray;
|
||||||
let trayIcon = "ac_plug_colored";
|
let trayIcon = "ac_plug_colored";
|
||||||
app.whenReady().then(async () => {
|
void app.whenReady().then(async () => {
|
||||||
let finishedSetup = await getConfig("doneSetup");
|
// REVIEW - app will hang at startup if line above is awaited.
|
||||||
if ((await getConfig("trayIcon")) != "default") {
|
const finishedSetup = getConfig("doneSetup");
|
||||||
trayIcon = await getConfig("trayIcon");
|
if (getConfig("trayIcon") != "default") {
|
||||||
|
trayIcon = getConfig("trayIcon");
|
||||||
}
|
}
|
||||||
let trayPath = nativeImage.createFromPath(path.join(import.meta.dirname, "../", `/assets/${trayIcon}.png`));
|
let trayPath = nativeImage.createFromPath(path.join(import.meta.dirname, "../", `/assets/${trayIcon}.png`));
|
||||||
let trayVerIcon;
|
const trayVerIcon = function () {
|
||||||
trayVerIcon = function () {
|
|
||||||
if (process.platform == "win32") {
|
if (process.platform == "win32") {
|
||||||
return trayPath.resize({height: 16});
|
return trayPath.resize({height: 16});
|
||||||
} else if (process.platform == "darwin") {
|
} else if (process.platform == "darwin") {
|
||||||
|
@ -26,8 +26,8 @@ app.whenReady().then(async () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (process.platform == "darwin" && trayPath.getSize().height > 22) trayPath = trayPath.resize({height: 22});
|
if (process.platform == "darwin" && trayPath.getSize().height > 22) trayPath = trayPath.resize({height: 22});
|
||||||
if (await getConfig("tray")) {
|
if (getConfig("tray")) {
|
||||||
let clientName = (await getConfig("clientName")) ?? "ArmCord";
|
const clientName = getConfig("clientName") ?? "ArmCord";
|
||||||
tray = new Tray(trayPath);
|
tray = new Tray(trayPath);
|
||||||
if (finishedSetup == false) {
|
if (finishedSetup == false) {
|
||||||
const contextMenu = Menu.buildFromTemplate([
|
const contextMenu = Menu.buildFromTemplate([
|
||||||
|
@ -37,8 +37,8 @@ app.whenReady().then(async () => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: `Quit ${clientName}`,
|
label: `Quit ${clientName}`,
|
||||||
async click() {
|
click() {
|
||||||
fs.unlink(await getConfigLocation(), (err) => {
|
fs.unlink(getConfigLocation(), (err) => {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
|
||||||
console.log('Closed during setup. "settings.json" was deleted');
|
console.log('Closed during setup. "settings.json" was deleted');
|
||||||
|
@ -50,6 +50,7 @@ app.whenReady().then(async () => {
|
||||||
tray.setContextMenu(contextMenu);
|
tray.setContextMenu(contextMenu);
|
||||||
} else {
|
} else {
|
||||||
const contextMenu = Menu.buildFromTemplate([
|
const contextMenu = Menu.buildFromTemplate([
|
||||||
|
// REVIEW - Awaiting any window creation will fail silently
|
||||||
{
|
{
|
||||||
label: `${clientName} ${getDisplayVersion()}`,
|
label: `${clientName} ${getDisplayVersion()}`,
|
||||||
icon: trayVerIcon(),
|
icon: trayVerIcon(),
|
||||||
|
@ -67,13 +68,13 @@ app.whenReady().then(async () => {
|
||||||
{
|
{
|
||||||
label: "Open Settings",
|
label: "Open Settings",
|
||||||
click() {
|
click() {
|
||||||
createSettingsWindow();
|
void createSettingsWindow();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Support Discord Server",
|
label: "Support Discord Server",
|
||||||
click() {
|
click() {
|
||||||
createInviteWindow("TnhxcqynZ2");
|
void createInviteWindow("TnhxcqynZ2");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -82,7 +83,8 @@ app.whenReady().then(async () => {
|
||||||
{
|
{
|
||||||
label: `Quit ${clientName}`,
|
label: `Quit ${clientName}`,
|
||||||
click() {
|
click() {
|
||||||
app.quit();
|
// NOTE - Temporary fix for app not actually quitting
|
||||||
|
app.exit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
@ -93,7 +95,7 @@ app.whenReady().then(async () => {
|
||||||
mainWindow.show();
|
mainWindow.show();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if ((await getConfig("tray")) == undefined) {
|
if (getConfig("tray") == undefined) {
|
||||||
if (process.platform == "linux") {
|
if (process.platform == "linux") {
|
||||||
const options: MessageBoxOptions = {
|
const options: MessageBoxOptions = {
|
||||||
type: "question",
|
type: "question",
|
||||||
|
@ -104,7 +106,7 @@ app.whenReady().then(async () => {
|
||||||
detail: "Linux may not work well with tray icons. Depending on your system configuration, you may not be able to see the tray icon. Enable at your own risk. Can be changed later."
|
detail: "Linux may not work well with tray icons. Depending on your system configuration, you may not be able to see the tray icon. Enable at your own risk. Can be changed later."
|
||||||
};
|
};
|
||||||
|
|
||||||
dialog.showMessageBox(mainWindow, options).then(({response}) => {
|
await dialog.showMessageBox(mainWindow, options).then(({response}) => {
|
||||||
if (response == 0) {
|
if (response == 0) {
|
||||||
setConfig("tray", true);
|
setConfig("tray", true);
|
||||||
} else {
|
} else {
|
||||||
|
|
21
src/types/armcordWindow.d.ts
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
export interface ArmCordWindow {
|
||||||
|
window: {
|
||||||
|
show: () => void;
|
||||||
|
hide: () => void;
|
||||||
|
minimize: () => void;
|
||||||
|
maximize: () => void;
|
||||||
|
};
|
||||||
|
titlebar: {
|
||||||
|
injectTitlebar: () => void;
|
||||||
|
isTitlebar: boolean;
|
||||||
|
};
|
||||||
|
electron: string;
|
||||||
|
channel: string;
|
||||||
|
setPingCount: (pingCount: number) => void;
|
||||||
|
setTrayIcon: (favicon: string) => void;
|
||||||
|
getLang: (toGet: string) => Promise<string>;
|
||||||
|
getDisplayMediaSelector: () => Promise<string>;
|
||||||
|
version: string;
|
||||||
|
mods: string;
|
||||||
|
openSettingsWindow: () => void;
|
||||||
|
}
|
1
src/types/i18nStrings.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export type i18nStrings = Record<string, string>;
|
31
src/types/settings.d.ts
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
export interface Settings {
|
||||||
|
// Referenced for detecting a broken config.
|
||||||
|
"0"?: string;
|
||||||
|
// Referenced once for disabling mod updating.
|
||||||
|
noBundleUpdates?: boolean;
|
||||||
|
// Only used for external url warning dialog.
|
||||||
|
ignoreProtocolWarning?: boolean;
|
||||||
|
customIcon: string;
|
||||||
|
windowStyle: string;
|
||||||
|
channel: string;
|
||||||
|
armcordCSP: boolean;
|
||||||
|
minimizeToTray: boolean;
|
||||||
|
multiInstance: boolean;
|
||||||
|
spellcheck: boolean;
|
||||||
|
mods: string;
|
||||||
|
dynamicIcon: boolean;
|
||||||
|
mobileMode: boolean;
|
||||||
|
skipSplash: boolean;
|
||||||
|
performanceMode: string;
|
||||||
|
customJsBundle: RequestInfo | URL;
|
||||||
|
customCssBundle: RequestInfo | URL;
|
||||||
|
startMinimized: boolean;
|
||||||
|
useLegacyCapturer: boolean;
|
||||||
|
tray: boolean;
|
||||||
|
keybinds: string[];
|
||||||
|
inviteWebsocket: boolean;
|
||||||
|
disableAutogain: boolean;
|
||||||
|
trayIcon: string;
|
||||||
|
doneSetup: boolean;
|
||||||
|
clientName: string;
|
||||||
|
}
|
16
src/types/themeManifest.d.ts
vendored
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
export interface ThemeManifest {
|
||||||
|
name: string;
|
||||||
|
author?: string;
|
||||||
|
description?: string;
|
||||||
|
version?: string;
|
||||||
|
invite?: string;
|
||||||
|
authorId?: string;
|
||||||
|
theme: string;
|
||||||
|
authorLink?: string;
|
||||||
|
donate?: string;
|
||||||
|
patreon?: string;
|
||||||
|
website?: string;
|
||||||
|
source?: string;
|
||||||
|
updateSrc?: string;
|
||||||
|
supportsArmCordTitlebar?: boolean;
|
||||||
|
}
|
7
src/types/windowState.d.ts
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export interface WindowState {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
isMaximized: boolean;
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
// Reference: https://www.typescriptlang.org/tsconfig
|
// Reference: https://www.typescriptlang.org/tsconfig
|
||||||
"include": ["src/**/*"], // This makes it so that the compiler won't compile anything outside of "src".
|
"include": ["src/**/*", "eslint.config.js", "prettier.config.js"], // This makes it so that the compiler won't compile anything outside of "src".
|
||||||
//"exclude": ["src/**/*.test.ts"], // Exclude .test.ts files since they're for Jest only.
|
//"exclude": ["src/**/*.test.ts"], // Exclude .test.ts files since they're for Jest only.
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
// Project Structure //
|
// Project Structure //
|
||||||
|
@ -14,10 +14,10 @@
|
||||||
"noFallthroughCasesInSwitch": true, // Prevents accidentally forgetting to break every switch case. Of course, if you know what you're doing, feel free to add a @ts-ignore, which also signals that it's not a mistake.
|
"noFallthroughCasesInSwitch": true, // Prevents accidentally forgetting to break every switch case. Of course, if you know what you're doing, feel free to add a @ts-ignore, which also signals that it's not a mistake.
|
||||||
"forceConsistentCasingInFileNames": true, // Make import paths case-sensitive. "./tEst" is no longer the same as "./test".
|
"forceConsistentCasingInFileNames": true, // Make import paths case-sensitive. "./tEst" is no longer the same as "./test".
|
||||||
"esModuleInterop": true, // Enables compatibility with Node.js' module system since the entire export can be whatever you want. allowSyntheticDefaultImports doesn't address runtime issues and is made redundant by this setting.
|
"esModuleInterop": true, // Enables compatibility with Node.js' module system since the entire export can be whatever you want. allowSyntheticDefaultImports doesn't address runtime issues and is made redundant by this setting.
|
||||||
"lib": ["ES2022", "DOM"], // Specifies what common libraries you have access to. If you're working in Node.js, you'll want to leave out the DOM library. But do make sure to include "@types/node" because otherwise, variables like "console" won't be defined.
|
"lib": ["ESNext", "DOM"], // Specifies what common libraries you have access to. If you're working in Node.js, you'll want to leave out the DOM library. But do make sure to include "@types/node" because otherwise, variables like "console" won't be defined.
|
||||||
"noUnusedLocals": true, // Warns you if you have unused variables.
|
"noUnusedLocals": true, // Warns you if you have unused variables.
|
||||||
// Output //
|
// Output //
|
||||||
"module": "ES2022", // Compiles ES6 imports to require() syntax.
|
"module": "ESNext", // Compiles ES6 imports to require() syntax.
|
||||||
"removeComments": false,
|
"removeComments": false,
|
||||||
"sourceMap": true, // Used for displaying the original source when debugging in webpack. Allows you to set breakpoints directly on TypeScript code for VSCode's debugger.
|
"sourceMap": true, // Used for displaying the original source when debugging in webpack. Allows you to set breakpoints directly on TypeScript code for VSCode's debugger.
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
"declarationMap": false, // Allows the user to go to the source file when hitting a go-to-implementation key like F12 in VSCode for example.
|
"declarationMap": false, // Allows the user to go to the source file when hitting a go-to-implementation key like F12 in VSCode for example.
|
||||||
//"declarationDir": "typings", // declarationDir allows you to separate the compiled code from the declaration files, used in conjunction with package.json's "types" property.
|
//"declarationDir": "typings", // declarationDir allows you to separate the compiled code from the declaration files, used in conjunction with package.json's "types" property.
|
||||||
// Web Compatibility //
|
// Web Compatibility //
|
||||||
"target": "ES2022", // ES2017 supports async/await, reducing the amount of compiled code, especially for async-heavy projects. ES2020 is from the Node 14 base (https://github.com/tsconfig/bases/blob/master/bases/node14.json)
|
"target": "ESNext", // ES2017 supports async/await, reducing the amount of compiled code, especially for async-heavy projects. ES2020 is from the Node 14 base (https://github.com/tsconfig/bases/blob/master/bases/node14.json)
|
||||||
"downlevelIteration": false, // This flag adds extra support when targeting ES3, but adds extra bloat otherwise.
|
"downlevelIteration": false, // This flag adds extra support when targeting ES3, but adds extra bloat otherwise.
|
||||||
"importHelpers": false // Reduce the amount of bloat that comes from downlevelIteration (when polyfills are redeclared).
|
"importHelpers": false // Reduce the amount of bloat that comes from downlevelIteration (when polyfills are redeclared).
|
||||||
}
|
}
|
||||||
|
|