Compare commits

..

6 Commits

Author SHA1 Message Date
allcontributors[bot]
2ce9309d1e update README.de-DE.md [skip ci] 2024-05-19 08:18:32 +00:00
allcontributors[bot]
14b4dd6182 update README.it-IT.md [skip ci] 2024-05-19 08:18:31 +00:00
allcontributors[bot]
4268302e88 update README.ko-KR.md [skip ci] 2024-05-19 08:18:30 +00:00
allcontributors[bot]
603b3a0a77 update README.ru-RU.md [skip ci] 2024-05-19 08:18:29 +00:00
allcontributors[bot]
2e79e9f2bc update README.zh-CN.md [skip ci] 2024-05-19 08:18:28 +00:00
allcontributors[bot]
888c3bb977 update README.md [skip ci] 2024-05-19 08:18:27 +00:00
92 changed files with 1434 additions and 1325 deletions

View File

@@ -1292,42 +1292,6 @@
"contributions": [
"code"
]
},
{
"login": "C41M50N",
"name": "Charles Buffington",
"avatar_url": "https://avatars.githubusercontent.com/u/29805363?v=4",
"profile": "https://cbuff.dev",
"contributions": [
"code"
]
},
{
"login": "GeminiLn",
"name": "Yu Qin",
"avatar_url": "https://avatars.githubusercontent.com/u/12425057?v=4",
"profile": "https://github.com/GeminiLn",
"contributions": [
"code"
]
},
{
"login": "fireblue",
"name": "fireblue",
"avatar_url": "https://avatars.githubusercontent.com/u/1034929?v=4",
"profile": "https://github.com/fireblue",
"contributions": [
"code"
]
},
{
"login": "marko1616",
"name": "marko1616",
"avatar_url": "https://avatars.githubusercontent.com/u/45327989?v=4",
"profile": "https://github.com/marko1616",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,

View File

@@ -13,11 +13,11 @@ jobs:
- name: Installing Node
uses: actions/setup-node@v3.7.0
with:
node-version: 18
node-version: 16
- name: Install deps
run: |
npm i -g yarn
npm i -g yarn@1.19.1
cd app
yarn
cd ..
@@ -37,15 +37,9 @@ jobs:
matrix:
include:
- arch: x86_64
rust_triple: x86_64-apple-darwin
- arch: arm64
rust_triple: aarch64-apple-darwin
fail-fast: false
env:
ARCH: ${{matrix.arch}}
RUST_TARGET_TRIPLE: ${{matrix.rust_triple}}
steps:
- name: Checkout
uses: actions/checkout@v3
@@ -55,18 +49,23 @@ jobs:
- name: Installing Node
uses: actions/setup-node@v3.7.0
with:
node-version: 18
- run: rustup target add ${{matrix.rust_triple}}
node-version: 16
- name: Install deps
run: |
sudo -H pip3 install setuptools
sudo npm i -g yarn
npm config set python python3
sudo npm i -g yarn@1.22.1
yarn --network-timeout 1000000
env:
ARCH: ${{matrix.arch}}
- name: Fix cross build
run: |
rm -rf app/node_modules/cpu-features
rm -rf app/node_modules/ssh2/crypto/build
if: matrix.arch == 'arm64'
- name: Webpack
run: yarn run build
@@ -138,24 +137,18 @@ jobs:
include:
- build-arch: x64
arch: amd64
rust_triple: x86_64-unknown-linux-gnu
- build-arch: arm64
arch: arm64
rust_triple: aarch64-unknown-linux-gnu
triplet: aarch64-linux-gnu-
- build-arch: arm
arch: armhf
rust_triple: arm-unknown-linux-gnueabihf
triplet: arm-linux-gnueabihf-
fail-fast: false
env:
CC: ${{matrix.triplet}}gcc
CXX: ${{matrix.triplet}}g++
ARCH: ${{matrix.build-arch}}
npm_config_arch: ${{matrix.build-arch}}
npm_config_target_arch: ${{matrix.build-arch}}
RUST_TARGET_TRIPLE: ${{matrix.rust_triple}}
steps:
- name: Checkout
@@ -168,51 +161,52 @@ jobs:
with:
node-version: 18
- run: rustup target add ${{matrix.rust_triple}}
- name: Install dependencies
- name: Install deps (amd64)
run: |
sudo apt-get update
sudo apt-get install libarchive-tools zsh crossbuild-essential-${{matrix.arch}}
- name: Setup tar to run as root
run: sudo chmod u+s "$(command -v tar)"
if: matrix.build-arch != 'x64'
- name: Download cached sysroot
uses: actions/cache@v3
id: dl-cached-sysroot
if: matrix.build-arch !='x64'
with:
key: sysroot-${{matrix.build-arch}}
path: /${{matrix.build-arch}}-sysroot
- name: Setup crossbuild sysroot
run: |
sudo apt-get update -y && sudo apt-get install debootstrap qemu-user-static binfmt-support -y
sudo qemu-debootstrap --include=libfontconfig1-dev,libsecret-1-dev,libnss3,libatk1.0-0,libatk-bridge2.0-0,libgdk-pixbuf2.0-0,libgtk-3-0,libgbm1 --variant=buildd --exclude=snapd --components=main,restricted,universe,multiverse --extractor=dpkg-deb --arch ${{matrix.arch}} bionic /${{matrix.build-arch}}-sysroot/ http://ports.ubuntu.com/ubuntu-ports/
sudo find /${{matrix.build-arch}}-sysroot -type l -lname '/*' -exec sh -c 'file="$0"; dir=$(dirname "$file"); target=$(readlink "$0"); prefix=$(dirname "$dir" | sed 's@[^/]*@\.\.@g'); newtarget="$prefix$target"; ln -snf $newtarget $file' {} \; ;
if: matrix.build-arch != 'x64' && steps.dl-cached-sysroot.outputs.cache-hit != 'true'
- name: Setup env to use ${{matrix.build-arch}} sysroot
run: |
echo "CFLAGS=--sysroot=/${{matrix.build-arch}}-sysroot/" >> $GITHUB_ENV
echo "CXXFLAGS=--sysroot=/${{matrix.build-arch}}-sysroot/" >> $GITHUB_ENV
echo "LDFLAGS=--sysroot=/${{matrix.build-arch}}-sysroot/" >> $GITHUB_ENV
[[ ${npm_config_arch} == 'arm' ]] && echo "npm_config_arch=armv7l" >> $GITHUB_ENV
if [[ ${{matrix.arch}} == 'armhf' ]]; then
echo "PKG_CONFIG_PATH=/${{matrix.build-arch}}-sysroot/usr/lib/pkgconfig/:/${{matrix.build-arch}}-sysroot/usr/lib/arm-linux-gnueabihf/pkgconfig/" >> $GITHUB_ENV
elif [[ ${{matrix.arch}} == 'arm64' ]]; then
echo "PKG_CONFIG_PATH=/${{matrix.build-arch}}-sysroot/usr/lib/pkgconfig/:/${{matrix.build-arch}}-sysroot/usr/lib/aarch64-linux-gnu/pkgconfig/" >> $GITHUB_ENV
fi
if: matrix.build-arch != 'x64'
sudo apt-get install libarchive-tools zsh python3-distutils
- name: Install npm_modules (amd64)
run: |
npm i -g yarn node-gyp
yarn --network-timeout 1000000 --arch=${{matrix.build-arch}} --target-arch=${{matrix.build-arch}}
npm i -g yarn
yarn --network-timeout 1000000
if: matrix.build-arch == 'x64'
- name: Setup Crossbuild (${{matrix.arch}})
run: |
sudo apt-get update -y && sudo apt-get install schroot sbuild debootstrap -y
sudo debootstrap --include=git,curl,gnupg,ca-certificates,crossbuild-essential-${{matrix.arch}},python-dev,python3-dev,libarchive-tools,cmake --variant=buildd --exclude=snapd --components=main,restricted,universe,multiverse --extractor=dpkg-deb bionic /build-chroot/
echo 'deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu bionic main restricted universe multiverse' | sudo tee /build-chroot/etc/apt/sources.list >/dev/null
echo 'deb [arch=arm64,armhf] http://ports.ubuntu.com/ubuntu-ports bionic main restricted universe multiverse' | sudo tee -a /build-chroot/etc/apt/sources.list >/dev/null
curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | gpg --dearmor | sudo tee /build-chroot/etc/apt/trusted.gpg.d/nodesource.gpg >/dev/null
echo 'deb http://deb.nodesource.com/node_16.x bionic main' | sudo tee /build-chroot/etc/apt/sources.list.d/nodesource.list >/dev/null
echo "[build-chroot]
description=Ubuntu 18.04 Build chroot
type=directory
directory=/build-chroot
root-groups=root,sudo
profile=buildd
personality=linux
union-type=overlay" | sudo tee /etc/schroot/chroot.d/build-chroot.pref >/dev/null
echo "/home /home none rw,bind 0 0" | sudo tee -a /etc/schroot/buildd/fstab >/dev/null
if: matrix.build-arch != 'x64'
- name: Install node_modules & CrossBuild native modules for ${{matrix.arch}}
run: |
sudo schroot -c build-chroot -u root -- bash -c "apt-get update -y
dpkg --add-architecture ${{matrix.arch}}
apt-get install -y nodejs libfontconfig-dev:${{matrix.arch}} libsecret-1-dev:${{matrix.arch}} libnss3:${{matrix.arch}} libatk1.0-0:${{matrix.arch}} libatk-bridge2.0-0:${{matrix.arch}} libgdk-pixbuf2.0-0:${{matrix.arch}} libgtk-3-0:${{matrix.arch}} libgbm1:${{matrix.arch}}
export CC=${{matrix.triplet}}gcc CXX=${{matrix.triplet}}g++ LD=${{matrix.triplet}}ld
if [[ ${{matrix.arch}} == 'arm64' ]]; then
export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/lib/aarch64-linux-gnu/pkgconfig/
elif [[ ${{matrix.arch}} == 'armhf' ]]; then
export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/lib/arm-linux-gnueabihf/pkgconfig/
fi
export ARCH=${{matrix.build-arch}} npm_config_arch=${{matrix.build-arch}} npm_config_target_arch=${{matrix.build-arch}}
npm i -g yarn
yarn --network-timeout 1000000 --arch=${{matrix.build-arch}} --target_arch=${{matrix.build-arch}}"
if: matrix.build-arch != 'x64'
- name: Webpack (${{matrix.arch}})
run: yarn run build --arch=${{matrix.build-arch}} --target_arch=${{matrix.build-arch}}
@@ -228,10 +222,57 @@ jobs:
USE_HARD_LINKS: false
# DEBUG: electron-builder,electron-builder:*
- name: Build web resources (amd64 only)
- name: Build web resources
run: zsh -c 'tar czf tabby-web.tar.gz (tabby-*|web)/dist'
if: matrix.build-arch == 'x64'
# - name: Install deps and Build (arm64)
# uses: docker://multiarch/ubuntu-core:arm64-bionic
# with:
# args: >
# bash -c
# "apt update && apt install curl lsb-release gnupg -y &&
# curl -fsSL https://deb.nodesource.com/setup_16.x | bash - &&
# apt install make build-essential git ruby libarchive-tools nodejs rpm libsecret-1-dev libfontconfig1-dev -y &&
# git config --global --add safe.directory /github/workspace &&
# gem install public_suffix -v 4.0.7 &&
# gem install fpm --no-document &&
# npm i -g yarn &&
# cd /github/workspace &&
# yarn --network-timeout 1000000 &&
# yarn run build &&
# scripts/prepackage-plugins.mjs &&
# USE_SYSTEM_FPM=true scripts/build-linux.mjs"
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# KEYGEN_TOKEN: ${{ secrets.KEYGEN_TOKEN }}
# USE_HARD_LINKS: false
# if: matrix.build-arch == 'arm64' && github.repository == 'Eugeny/tabby' && startsWith(github.ref, 'refs/tags')
# - name: Install deps and Build (armv7l)
# uses: docker://multiarch/ubuntu-core:armhf-bionic
# with:
# args: >
# bash -c
# "apt update && apt install curl lsb-release gnupg -y &&
# curl -fsSL https://deb.nodesource.com/setup_16.x | bash - &&
# apt install make build-essential git ruby libarchive-tools nodejs rpm libsecret-1-dev libfontconfig1-dev -y &&
# git config --global --add safe.directory /github/workspace &&
# gem install public_suffix -v 4.0.7 &&
# gem install fpm --no-document &&
# npm i -g yarn &&
# cd /github/workspace &&
# sed -i '/ \"electron\":/c\ \"electron\": \"17.0.0\",' package.json &&
# yarn --network-timeout 1000000 &&
# yarn run build &&
# scripts/prepackage-plugins.mjs &&
# USE_SYSTEM_FPM=true scripts/build-linux.mjs"
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# KEYGEN_TOKEN: ${{ secrets.KEYGEN_TOKEN }}
# USE_HARD_LINKS: false
# if: matrix.build-arch == 'arm' && github.repository == 'Eugeny/tabby' && startsWith(github.ref, 'refs/tags')
- name: Upload symbols (amd64 only)
run: |
sudo npm install -g @sentry/cli --unsafe-perm
@@ -243,15 +284,13 @@ jobs:
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
- name: Upload packages to packagecloud.io
uses: TykTechnologies/packagecloud-action@main
uses: Eugeny/packagecloud-action@main
if: github.repository == 'Eugeny/tabby' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
env:
PACKAGECLOUD_TOKEN: ${{ secrets.PACKAGECLOUD_TOKEN }}
with:
repo: 'eugeny/tabby'
dir: 'dist'
rpmvers: 'el/9 el/8 ol/6 ol/7'
debvers: 'ubuntu/bionic ubuntu/focal ubuntu/hirsute ubuntu/impish ubuntu/jammy ubuntu/kinetic ubuntu/noble debian/jessie debian/stretch debian/buster'
- uses: actions/upload-artifact@master
name: Upload AppImage (${{matrix.arch}})
@@ -290,22 +329,17 @@ jobs:
path: tabby-web.tar.gz
if: matrix.build-arch == 'x64'
Windows-Build:
runs-on: windows-latest
runs-on: windows-2022
needs: Lint
strategy:
matrix:
include:
- arch: x64
rust_triple: x86_64-pc-windows-msvc
- arch: arm64
rust_triple: aarch64-pc-windows-msvc
fail-fast: false
env:
RUST_TARGET_TRIPLE: ${{matrix.rust_triple}}
ARCH: ${{matrix.arch}}
steps:
- name: Checkout
uses: actions/checkout@v3
@@ -315,20 +349,17 @@ jobs:
- name: Installing Node
uses: actions/setup-node@v3.7.0
with:
node-version: 18
- run: npm i -g npx
- run: rustup target add ${{matrix.rust_triple}}
node-version: 16
- name: Update node-gyp
run: |
npm install --global node-gyp@10.2.0
npm install --global node-gyp@8.4.1
npm prefix -g | % {npm config set node_gyp "$_\node_modules\node-gyp\bin\node-gyp.js"}
- name: Build
shell: powershell
run: |
npm i -g yar node-gyp
npm i -g yarn@1.19.1
yarn --network-timeout 1000000
yarn run build
node scripts/prepackage-plugins.mjs
@@ -348,7 +379,7 @@ jobs:
- name: Build packages without signing
run: node scripts/build-windows.mjs
if: "! (github.repository == 'Eugeny/tabby' && github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags')))"
if: "!(github.repository == 'Eugeny/tabby' && github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags')))"
env:
ARCH: ${{matrix.arch}}

1
.gitignore vendored
View File

@@ -33,6 +33,7 @@ docs/api
sentry.properties
sentry-symbols.js
tabby-ssh/util/pagent.exe
*.psd
crowdin.yml

View File

@@ -338,10 +338,7 @@ Dank geht an diese wunderbaren Menschen ([emoji key](https://allcontributors.org
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://5k.work/"><img src="https://avatars.githubusercontent.com/u/82694310?v=4?s=100" width="100px;" alt="Mxmilu"/><br /><sub><b>Mxmilu</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=Mxmilu666" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://cbuff.dev"><img src="https://avatars.githubusercontent.com/u/29805363?v=4?s=100" width="100px;" alt="Charles Buffington"/><br /><sub><b>Charles Buffington</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=C41M50N" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GeminiLn"><img src="https://avatars.githubusercontent.com/u/12425057?v=4?s=100" width="100px;" alt="Yu Qin"/><br /><sub><b>Yu Qin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=GeminiLn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fireblue"><img src="https://avatars.githubusercontent.com/u/1034929?v=4?s=100" width="100px;" alt="fireblue"/><br /><sub><b>fireblue</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=fireblue" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marko1616"><img src="https://avatars.githubusercontent.com/u/45327989?v=4?s=100" width="100px;" alt="marko1616"/><br /><sub><b>marko1616</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=marko1616" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/RiccardoManzan"><img src="https://avatars.githubusercontent.com/u/59048102?v=4?s=100" width="100px;" alt="Riccardo Manzan"/><br /><sub><b>Riccardo Manzan</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=RiccardoManzan" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -340,10 +340,6 @@ Gracias a estas maravillosas personas ([emoji key](https://allcontributors.org/d
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://5k.work/"><img src="https://avatars.githubusercontent.com/u/82694310?v=4?s=100" width="100px;" alt="Mxmilu"/><br /><sub><b>Mxmilu</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=Mxmilu666" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://cbuff.dev"><img src="https://avatars.githubusercontent.com/u/29805363?v=4?s=100" width="100px;" alt="Charles Buffington"/><br /><sub><b>Charles Buffington</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=C41M50N" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GeminiLn"><img src="https://avatars.githubusercontent.com/u/12425057?v=4?s=100" width="100px;" alt="Yu Qin"/><br /><sub><b>Yu Qin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=GeminiLn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fireblue"><img src="https://avatars.githubusercontent.com/u/1034929?v=4?s=100" width="100px;" alt="fireblue"/><br /><sub><b>fireblue</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=fireblue" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marko1616"><img src="https://avatars.githubusercontent.com/u/45327989?v=4?s=100" width="100px;" alt="marko1616"/><br /><sub><b>marko1616</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=marko1616" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -337,10 +337,6 @@ Terima kasih kepada mereka yang telah membantu ([emoji key](https://allcontribut
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://5k.work/"><img src="https://avatars.githubusercontent.com/u/82694310?v=4?s=100" width="100px;" alt="Mxmilu"/><br /><sub><b>Mxmilu</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=Mxmilu666" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://cbuff.dev"><img src="https://avatars.githubusercontent.com/u/29805363?v=4?s=100" width="100px;" alt="Charles Buffington"/><br /><sub><b>Charles Buffington</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=C41M50N" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GeminiLn"><img src="https://avatars.githubusercontent.com/u/12425057?v=4?s=100" width="100px;" alt="Yu Qin"/><br /><sub><b>Yu Qin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=GeminiLn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fireblue"><img src="https://avatars.githubusercontent.com/u/1034929?v=4?s=100" width="100px;" alt="fireblue"/><br /><sub><b>fireblue</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=fireblue" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marko1616"><img src="https://avatars.githubusercontent.com/u/45327989?v=4?s=100" width="100px;" alt="marko1616"/><br /><sub><b>marko1616</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=marko1616" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -333,10 +333,7 @@ Grazie a queste persone meravigliose ([emoji key](https://allcontributors.org/do
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://5k.work/"><img src="https://avatars.githubusercontent.com/u/82694310?v=4?s=100" width="100px;" alt="Mxmilu"/><br /><sub><b>Mxmilu</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=Mxmilu666" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://cbuff.dev"><img src="https://avatars.githubusercontent.com/u/29805363?v=4?s=100" width="100px;" alt="Charles Buffington"/><br /><sub><b>Charles Buffington</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=C41M50N" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GeminiLn"><img src="https://avatars.githubusercontent.com/u/12425057?v=4?s=100" width="100px;" alt="Yu Qin"/><br /><sub><b>Yu Qin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=GeminiLn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fireblue"><img src="https://avatars.githubusercontent.com/u/1034929?v=4?s=100" width="100px;" alt="fireblue"/><br /><sub><b>fireblue</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=fireblue" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marko1616"><img src="https://avatars.githubusercontent.com/u/45327989?v=4?s=100" width="100px;" alt="marko1616"/><br /><sub><b>marko1616</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=marko1616" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/RiccardoManzan"><img src="https://avatars.githubusercontent.com/u/59048102?v=4?s=100" width="100px;" alt="Riccardo Manzan"/><br /><sub><b>Riccardo Manzan</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=RiccardoManzan" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -348,10 +348,6 @@ Windows上では、`Tabby.exe`がある場所と同じ場所に`data`フォル
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://5k.work/"><img src="https://avatars.githubusercontent.com/u/82694310?v=4?s=100" width="100px;" alt="Mxmilu"/><br /><sub><b>Mxmilu</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=Mxmilu666" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://cbuff.dev"><img src="https://avatars.githubusercontent.com/u/29805363?v=4?s=100" width="100px;" alt="Charles Buffington"/><br /><sub><b>Charles Buffington</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=C41M50N" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GeminiLn"><img src="https://avatars.githubusercontent.com/u/12425057?v=4?s=100" width="100px;" alt="Yu Qin"/><br /><sub><b>Yu Qin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=GeminiLn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fireblue"><img src="https://avatars.githubusercontent.com/u/1034929?v=4?s=100" width="100px;" alt="fireblue"/><br /><sub><b>fireblue</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=fireblue" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marko1616"><img src="https://avatars.githubusercontent.com/u/45327989?v=4?s=100" width="100px;" alt="marko1616"/><br /><sub><b>marko1616</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=marko1616" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -332,10 +332,7 @@ Pull requests and plugins are welcome!
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://5k.work/"><img src="https://avatars.githubusercontent.com/u/82694310?v=4?s=100" width="100px;" alt="Mxmilu"/><br /><sub><b>Mxmilu</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=Mxmilu666" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://cbuff.dev"><img src="https://avatars.githubusercontent.com/u/29805363?v=4?s=100" width="100px;" alt="Charles Buffington"/><br /><sub><b>Charles Buffington</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=C41M50N" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GeminiLn"><img src="https://avatars.githubusercontent.com/u/12425057?v=4?s=100" width="100px;" alt="Yu Qin"/><br /><sub><b>Yu Qin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=GeminiLn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fireblue"><img src="https://avatars.githubusercontent.com/u/1034929?v=4?s=100" width="100px;" alt="fireblue"/><br /><sub><b>fireblue</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=fireblue" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marko1616"><img src="https://avatars.githubusercontent.com/u/45327989?v=4?s=100" width="100px;" alt="marko1616"/><br /><sub><b>marko1616</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=marko1616" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/RiccardoManzan"><img src="https://avatars.githubusercontent.com/u/59048102?v=4?s=100" width="100px;" alt="Riccardo Manzan"/><br /><sub><b>Riccardo Manzan</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=RiccardoManzan" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -2,7 +2,7 @@
<p align="center">
<a href="https://github.com/Eugeny/tabby/releases/latest"><img alt="GitHub All Releases" src="https://img.shields.io/github/downloads/eugeny/tabby/total.svg?label=DOWNLOADS&logo=github&style=for-the-badge"></a> &nbsp; <a href="https://nightly.link/Eugeny/tabby/workflows/build/master"><img src="https://shields.io/badge/-Nightly%20Builds-orange?logo=hackthebox&logoColor=fff&style=for-the-badge"/></a> &nbsp; <a href="https://discord.gg/Vn7BjmzhtF"><img alt="Discord" src="https://img.shields.io/discord/1280890060195233934?style=for-the-badge&color=blue&logo=discord&logoColor=white&label=Discord"></a> &nbsp <a href="https://translate.tabby.sh/"><img alt="Translate" src="https://shields.io/badge/Translate-UI-white?logo=googletranslate&style=for-the-badge&color=white&logoColor=fff"></a>
<a href="https://github.com/Eugeny/tabby/releases/latest"><img alt="GitHub All Releases" src="https://img.shields.io/github/downloads/eugeny/tabby/total.svg?label=DOWNLOADS&logo=github&style=for-the-badge"></a> &nbsp; <a href="https://nightly.link/Eugeny/tabby/workflows/build/master"><img src="https://shields.io/badge/-Nightly%20Builds-orange?logo=hackthebox&logoColor=fff&style=for-the-badge"/></a> &nbsp; <a href="https://matrix.to/#/#tabby-general:matrix.org"><img alt="Matrix" src="https://img.shields.io/matrix/tabby-general:matrix.org?logo=matrix&style=for-the-badge&color=magenta"></a> &nbsp <a href="https://translate.tabby.sh/"><img alt="Translate" src="https://shields.io/badge/Translate-UI-white?logo=googletranslate&style=for-the-badge&color=white&logoColor=fff"></a> &nbsp; <a href="https://twitter.com/eugeeeeny"><img alt="Twitter" src="https://shields.io/badge/Subscribe-News-blue?logo=twitter&style=for-the-badge&color=blue"></a>
</p>
<p align="center">
@@ -16,6 +16,9 @@
> 👋 Managing remote environments? Check out [Warpgate, my smart SSH/HTTP/MySQL bastion server](https://github.com/warp-tech/warpgate), it works great with Tabby, you'll love it.
> 👋 [Tabby-web](https://github.com/Eugeny/tabby-web) is looking for sponsors. As I can't afford to host it myself any longer, I'm looking for a sponsor to cover the hosting costs. If you're interested, please [get in touch](https://twitter.com/eugeeeeny)!
----
### Downloads:
@@ -152,11 +155,6 @@ Plugins and themes can be installed directly from the Settings view inside Tabby
[**keygen**](https://keygen.sh/?via=eugene) has provided free release & auto-update hosting
<a href="https://iqhive.com/"><img src="https://private-user-images.githubusercontent.com/161476/361584584-ed292436-1d50-46bc-b479-78222c83ed22.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjQ3MDg3NjgsIm5iZiI6MTcyNDcwODQ2OCwicGF0aCI6Ii8xNjE0NzYvMzYxNTg0NTg0LWVkMjkyNDM2LTFkNTAtNDZiYy1iNDc5LTc4MjIyYzgzZWQyMi5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwODI2JTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDgyNlQyMTQxMDhaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT1iYzNlZjIxN2JiYzBkYTU5NGE4YmZlZDJiNmIxZWE1ZTAyOTNhYjJlZTRhOGZjYTk4N2E4MzMzZjg0ZTNkZWQ0JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.pQzR2d71YV4TIxOH3Lg20HpNKrm_r2D-xfkEM_F2DTs" width="100"></a>
[**IQ Hive**](https://iqhive.com) is providing financial support for the project development
<a name="contributing"></a>
# Contributing
@@ -356,10 +354,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://5k.work/"><img src="https://avatars.githubusercontent.com/u/82694310?v=4?s=100" width="100px;" alt="Mxmilu"/><br /><sub><b>Mxmilu</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=Mxmilu666" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://cbuff.dev"><img src="https://avatars.githubusercontent.com/u/29805363?v=4?s=100" width="100px;" alt="Charles Buffington"/><br /><sub><b>Charles Buffington</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=C41M50N" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GeminiLn"><img src="https://avatars.githubusercontent.com/u/12425057?v=4?s=100" width="100px;" alt="Yu Qin"/><br /><sub><b>Yu Qin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=GeminiLn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fireblue"><img src="https://avatars.githubusercontent.com/u/1034929?v=4?s=100" width="100px;" alt="fireblue"/><br /><sub><b>fireblue</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=fireblue" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marko1616"><img src="https://avatars.githubusercontent.com/u/45327989?v=4?s=100" width="100px;" alt="marko1616"/><br /><sub><b>marko1616</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=marko1616" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/RiccardoManzan"><img src="https://avatars.githubusercontent.com/u/59048102?v=4?s=100" width="100px;" alt="Riccardo Manzan"/><br /><sub><b>Riccardo Manzan</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=RiccardoManzan" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -341,10 +341,6 @@ Obrigado vai para essas pessoas maravilhosas ([emoji key](https://allcontributor
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://5k.work/"><img src="https://avatars.githubusercontent.com/u/82694310?v=4?s=100" width="100px;" alt="Mxmilu"/><br /><sub><b>Mxmilu</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=Mxmilu666" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://cbuff.dev"><img src="https://avatars.githubusercontent.com/u/29805363?v=4?s=100" width="100px;" alt="Charles Buffington"/><br /><sub><b>Charles Buffington</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=C41M50N" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GeminiLn"><img src="https://avatars.githubusercontent.com/u/12425057?v=4?s=100" width="100px;" alt="Yu Qin"/><br /><sub><b>Yu Qin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=GeminiLn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fireblue"><img src="https://avatars.githubusercontent.com/u/1034929?v=4?s=100" width="100px;" alt="fireblue"/><br /><sub><b>fireblue</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=fireblue" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marko1616"><img src="https://avatars.githubusercontent.com/u/45327989?v=4?s=100" width="100px;" alt="marko1616"/><br /><sub><b>marko1616</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=marko1616" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -29,7 +29,7 @@
[**Tabby**](https://tabby.sh) (ранее **Terminus**) — широко конфигурируемый эмулятор терминала, SSH- и COM-клиент для Windows, macOS и Linux:
* Встроенный SSH- и Telnet-клиент и менеджер подключений;
* Встроенный последовательный терминал;
* Встроенный последовтаельный терминал;
* Темы и цветовые схемы;
* Полностью настраеваемые сочетания клавиш;
* Панели;
@@ -39,7 +39,7 @@
* Полная поддержка Unicode, включая символы двойной ширины;
* Не задыхается при быстром выводе;
* Полноценный опыт работы с shell на Windows, включая дополнение слов и команд по Tab (при помощи Clink);
* Встроенное защищённое хранилище для SSH-ключей и настроек;
* Втроенное защищённое хранилище для SSH-ключей и настроек;
* SSH-, SFTP- и Telnet-клиент доступен как [веб-приложение](https://tabby.sh/app) (также для [самостоятелньного хостинга](https://github.com/Eugeny/tabby-web)).
# Содержание <!-- omit in toc -->
@@ -71,7 +71,7 @@
* Деление окна на несколько панелей;
* Вкладки на любой стороне окна;
* Опционально закрепляемое окно с глобальной горячей клавишей для вызова («Quake console»);
* Определение прогресса выполняемого процесса;
* Определение прогресса процесса;
* Уведомления о завершении процессов;
* Защита от выполнения команд при вставке, предупреждения о вставке нескольких строк;
* Лигатуры шрифтов;
@@ -94,7 +94,7 @@
* Сохранение соединений;
* Поддержка ввода readline;
* Опциональный побатный ввод HEX и вывод hexdump;
* Опциональый побатный ввод HEX и вывод hexdump;
* Преобразование newline;
* Автоматическое восстановление соединения.
@@ -115,9 +115,9 @@
* [save-output](https://github.com/Eugeny/tabby-save-output) — запись вывода терминала в файл;
* [sync-config](https://github.com/starxg/terminus-sync-config) — синхронизация конфига в Gist или Gitee;
* [clippy](https://github.com/Eugeny/tabby-clippy) — плагин-пример, который постоянно будет вас бесить;
* [workspace-manager](https://github.com/composer404/tabby-workspace-manager) — позволяет создавать пользовательские профили рабочего окружения на основе конфига;
* [workspace-manager](https://github.com/composer404/tabby-workspace-manager) — позволяет создавать пользовательские провили рабочего окружеиня на основе конфига;
* [search-in-browser](https://github.com/composer404/tabby-search-in-browser) — открывает браузер по умолчанию с текстом, выделенном во вкладке Tabby.
* [sftp-tab](https://github.com/wljince007/tabby-sftp-tab) - открывает sftp вкладку для ssh соединения, похож на SecureCRT
* [sftp-tab](https://github.com/wljince007/tabby-sftp-tab) - open sftp tab for ssh connection like SecureCRT
<a name="themes"></a>
# Темы
@@ -333,10 +333,7 @@ Pull-запросы и плагины приветствуются!
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://5k.work/"><img src="https://avatars.githubusercontent.com/u/82694310?v=4?s=100" width="100px;" alt="Mxmilu"/><br /><sub><b>Mxmilu</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=Mxmilu666" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://cbuff.dev"><img src="https://avatars.githubusercontent.com/u/29805363?v=4?s=100" width="100px;" alt="Charles Buffington"/><br /><sub><b>Charles Buffington</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=C41M50N" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GeminiLn"><img src="https://avatars.githubusercontent.com/u/12425057?v=4?s=100" width="100px;" alt="Yu Qin"/><br /><sub><b>Yu Qin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=GeminiLn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fireblue"><img src="https://avatars.githubusercontent.com/u/1034929?v=4?s=100" width="100px;" alt="fireblue"/><br /><sub><b>fireblue</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=fireblue" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marko1616"><img src="https://avatars.githubusercontent.com/u/45327989?v=4?s=100" width="100px;" alt="marko1616"/><br /><sub><b>marko1616</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=marko1616" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/RiccardoManzan"><img src="https://avatars.githubusercontent.com/u/59048102?v=4?s=100" width="100px;" alt="Riccardo Manzan"/><br /><sub><b>Riccardo Manzan</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=RiccardoManzan" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -332,10 +332,7 @@
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://5k.work/"><img src="https://avatars.githubusercontent.com/u/82694310?v=4?s=100" width="100px;" alt="Mxmilu"/><br /><sub><b>Mxmilu</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=Mxmilu666" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://cbuff.dev"><img src="https://avatars.githubusercontent.com/u/29805363?v=4?s=100" width="100px;" alt="Charles Buffington"/><br /><sub><b>Charles Buffington</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=C41M50N" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GeminiLn"><img src="https://avatars.githubusercontent.com/u/12425057?v=4?s=100" width="100px;" alt="Yu Qin"/><br /><sub><b>Yu Qin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=GeminiLn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fireblue"><img src="https://avatars.githubusercontent.com/u/1034929?v=4?s=100" width="100px;" alt="fireblue"/><br /><sub><b>fireblue</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=fireblue" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marko1616"><img src="https://avatars.githubusercontent.com/u/45327989?v=4?s=100" width="100px;" alt="marko1616"/><br /><sub><b>marko1616</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=marko1616" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/RiccardoManzan"><img src="https://avatars.githubusercontent.com/u/59048102?v=4?s=100" width="100px;" alt="Riccardo Manzan"/><br /><sub><b>Riccardo Manzan</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=RiccardoManzan" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -183,10 +183,9 @@ export class Application {
}
enableTray (): void {
if (!!this.tray || process.platform === 'linux' || (this.configStore.hideTray ?? false) === true) {
if (!!this.tray || process.platform === 'linux') {
return
}
if (process.platform === 'darwin') {
this.tray = new Tray(`${app.getAppPath()}/assets/tray-darwinTemplate.png`)
this.tray.setPressedImage(`${app.getAppPath()}/assets/tray-darwinHighlightTemplate.png`)

View File

@@ -31,8 +31,6 @@ try {
app.exit(1)
}
process.mainModule = module
const application = new Application(configStore)
ipcMain.on('app:new-window', () => {

View File

@@ -1,5 +1,5 @@
import * as glasstron from 'glasstron'
import { autoUpdater } from 'electron-updater'
import { Subject, Observable, debounceTime } from 'rxjs'
import { BrowserWindow, app, ipcMain, Rectangle, Menu, screen, BrowserWindowConstructorOptions, TouchBar, nativeImage, WebContents } from 'electron'
import ElectronConfig = require('electron-config')
@@ -26,7 +26,7 @@ abstract class GlasstronWindow extends BrowserWindow {
abstract setBlur (_: boolean)
}
const macOSVibrancyType: any = process.platform === 'darwin' ? compareVersions(macOSRelease().version || '0.0', '10.14', '>=') ? 'fullscreen-ui' : 'dark' : null
const macOSVibrancyType: any = process.platform === 'darwin' ? compareVersions(macOSRelease().version || '0.0', '10.14', '>=') ? 'under-window' : 'dark' : null
const activityIcon = nativeImage.createFromPath(`${app.getAppPath()}/assets/activity.png`)
@@ -159,7 +159,6 @@ export class Window {
}
this.setupWindowManagement()
this.setupUpdater()
this.ready = new Promise(resolve => {
const listener = event => {
@@ -347,8 +346,11 @@ export class Window {
this.send('host:window-focused')
})
this.on('ready', () => {
this.window?.webContents.send('start', {
ipcMain.on('ready', event => {
if (!this.window || event.sender !== this.window.webContents) {
return
}
this.window.webContents.send('start', {
config: this.configStore,
executable: app.getPath('exe'),
windowID: this.window.id,
@@ -357,26 +359,42 @@ export class Window {
})
})
this.on('window-minimize', () => {
this.window?.minimize()
ipcMain.on('window-minimize', event => {
if (!this.window || event.sender !== this.window.webContents) {
return
}
this.window.minimize()
})
this.on('window-set-bounds', (_, bounds) => {
this.window?.setBounds(bounds)
ipcMain.on('window-set-bounds', (event, bounds) => {
if (!this.window || event.sender !== this.window.webContents) {
return
}
this.window.setBounds(bounds)
})
this.on('window-set-always-on-top', (_, flag) => {
this.window?.setAlwaysOnTop(flag)
ipcMain.on('window-set-always-on-top', (event, flag) => {
if (!this.window || event.sender !== this.window.webContents) {
return
}
this.window.setAlwaysOnTop(flag)
})
this.on('window-set-vibrancy', (_, enabled, type) => {
ipcMain.on('window-set-vibrancy', (event, enabled, type) => {
if (!this.window || event.sender !== this.window.webContents) {
return
}
this.setVibrancy(enabled, type)
})
this.on('window-set-window-controls-color', (_, theme) => {
ipcMain.on('window-set-window-controls-color', (event, theme) => {
if (!this.window || event.sender !== this.window.webContents) {
return
}
if (process.platform === 'win32') {
const symbolColor: string = theme.foreground
this.window?.setTitleBarOverlay(
this.window.setTitleBarOverlay(
{
symbolColor: symbolColor,
height: 32,
@@ -385,23 +403,32 @@ export class Window {
}
})
this.on('window-set-title', (_, title) => {
this.window?.setTitle(title)
ipcMain.on('window-set-title', (event, title) => {
if (!this.window || event.sender !== this.window.webContents) {
return
}
this.window.setTitle(title)
})
this.on('window-bring-to-front', () => {
if (this.window?.isMinimized()) {
ipcMain.on('window-bring-to-front', event => {
if (!this.window || event.sender !== this.window.webContents) {
return
}
if (this.window.isMinimized()) {
this.window.restore()
}
this.present()
})
this.on('window-close', () => {
ipcMain.on('window-close', event => {
if (!this.window || event.sender !== this.window.webContents) {
return
}
this.closing = true
this.window.close()
})
this.on('window-set-touch-bar', (_, segments, selectedIndex) => {
ipcMain.on('window-set-touch-bar', (_event, segments, selectedIndex) => {
this.touchBarControl.segments = segments.map(s => ({
label: s.label,
icon: s.hasActivity ? activityIcon : undefined,
@@ -441,46 +468,8 @@ export class Window {
this.window.setOpacity(opacity)
})
this.on('window-set-progress-bar', (_, value) => {
this.window?.setProgressBar(value, { mode: value < 0 ? 'none' : 'normal' })
})
}
on (event: string, listener: (...args: any[]) => void): void {
ipcMain.on(event, (e, ...args) => {
if (!this.window || e.sender !== this.window.webContents) {
return
}
listener(e, ...args)
})
}
private setupUpdater () {
autoUpdater.autoDownload = true
autoUpdater.autoInstallOnAppQuit = true
autoUpdater.on('update-available', () => {
this.send('updater:update-available')
})
autoUpdater.on('update-not-available', () => {
this.send('updater:update-not-available')
})
autoUpdater.on('error', err => {
this.send('updater:error', err)
})
autoUpdater.on('update-downloaded', () => {
this.send('updater:update-downloaded')
})
this.on('updater:check-for-updates', () => {
autoUpdater.checkForUpdates()
})
this.on('updater:quit-and-install', () => {
autoUpdater.quitAndInstall()
ipcMain.on('window-set-progress-bar', (_event, value) => {
this.window.setProgressBar(value, { mode: value < 0 ? 'none' : 'normal' })
})
}

View File

@@ -15,8 +15,8 @@
"watch": "webpack --progress --color --watch"
},
"dependencies": {
"@electron/remote": "^2",
"node-pty": "^1.1.0-beta.14",
"@electron/remote": "2.0.10",
"node-pty": "^1.0",
"any-promise": "^1.3.0",
"electron-config": "2.0.0",
"electron-debug": "^3.2.0",
@@ -30,7 +30,6 @@
"native-process-working-directory": "^1.0.2",
"npm": "6",
"rxjs": "^7.5.7",
"russh": "0.0.3",
"source-map-support": "^0.5.20",
"v8-compile-cache": "^2.3.0",
"yargs": "^17.7.2"
@@ -64,7 +63,8 @@
"tabby-terminal": "*"
},
"resolutions": {
"*/node-abi": "^3",
"node-gyp": "^10.0.0"
"*/node-abi": "^2.20.0",
"node-gyp": "^10.0.0",
"nan": "github:jkleinsc/nan#remove_accessor_signature"
}
}

View File

@@ -42,7 +42,6 @@ const config = {
'electron-config': 'commonjs electron-config',
'electron-debug': 'commonjs electron-debug',
'electron-promise-ipc': 'commonjs electron-promise-ipc',
'electron-updater': 'commonjs electron-updater',
fs: 'commonjs fs',
glasstron: 'commonjs glasstron',
mz: 'commonjs mz',

View File

@@ -2,10 +2,10 @@
# yarn lockfile v1
"@electron/remote@^2":
version "2.1.2"
resolved "https://registry.yarnpkg.com/@electron/remote/-/remote-2.1.2.tgz#52a97c8faa5b769155b649ef262f2f8c851776e6"
integrity sha512-EPwNx+nhdrTBxyCqXt/pftoQg/ybtWDW3DUWHafejvnB1ZGGfMpv6e15D8KeempocjXe78T7WreyGGb3mlZxdA==
"@electron/remote@2.0.10":
version "2.0.10"
resolved "https://registry.yarnpkg.com/@electron/remote/-/remote-2.0.10.tgz#133e2f607b1861ac249bd78b5abd1e961feed713"
integrity sha512-3SFKKaQXcyWgwmibud+UqJl/XlHOgLcI3fwtB9pNelPSJAcTxocOJrF6FaxBIQaj1+R05Di6xuAswZpXAW7xhA==
"@iarna/cli@^1.2.0":
version "1.2.0"
@@ -28,11 +28,6 @@
wrap-ansi "^8.1.0"
wrap-ansi-cjs "npm:wrap-ansi@^7.0.0"
"@napi-rs/cli@^2.18.3":
version "2.18.4"
resolved "https://registry.yarnpkg.com/@napi-rs/cli/-/cli-2.18.4.tgz#12bebfb7995902fa7ab43cc0b155a7f5a2caa873"
integrity sha512-SgJeA4df9DE2iAEpr3M2H0OKl/yjtg1BnRI5/JyowS71tUWhrfSu2LT0V3vlHET+g1hBVlrO60PmEXwUEKp8Mg==
"@ngx-translate/core@^14.0.0":
version "14.0.0"
resolved "https://registry.yarnpkg.com/@ngx-translate/core/-/core-14.0.0.tgz#af421d0e1a28376843f0fed375cd2fae7630a5ff"
@@ -1495,26 +1490,25 @@ glasstron@0.1.1:
x11 "^2.3.0"
glob@^10.2.2, glob@^10.3.10:
version "10.4.5"
resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956"
integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==
version "10.3.10"
resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.10.tgz#0351ebb809fd187fe421ab96af83d3a70715df4b"
integrity sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==
dependencies:
foreground-child "^3.1.0"
jackspeak "^3.1.2"
minimatch "^9.0.4"
minipass "^7.1.2"
package-json-from-dist "^1.0.0"
path-scurry "^1.11.1"
jackspeak "^2.3.5"
minimatch "^9.0.1"
minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
path-scurry "^1.10.1"
glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
version "7.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
version "7.1.6"
resolved "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.1.1"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
@@ -1937,10 +1931,10 @@ isstream@~0.1.2:
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz"
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
jackspeak@^3.1.2:
version "3.4.3"
resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a"
integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==
jackspeak@^2.3.5:
version "2.3.6"
resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8"
integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==
dependencies:
"@isaacs/cliui" "^8.0.2"
optionalDependencies:
@@ -2292,18 +2286,13 @@ lowercase-keys@^1.0.0:
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==
lru-cache@^10.0.1:
lru-cache@^10.0.1, "lru-cache@^9.1.1 || ^10.0.0":
version "10.0.2"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.2.tgz#34504678cc3266b09b8dfd6fab4e1515258271b7"
integrity sha512-Yj9mA8fPiVgOUpByoTZO5pNrcl5Yk37FcSHsUINpAsaBIEZIuqcCclDZJCVxqQShDsmYX8QG63svJiTbOATZwg==
dependencies:
semver "^7.3.5"
lru-cache@^10.2.0:
version "10.4.3"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119"
integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==
lru-cache@^4.0.1:
version "4.1.5"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
@@ -2423,17 +2412,10 @@ minimatch@^3.0.4:
dependencies:
brace-expansion "^1.1.7"
minimatch@^3.1.1:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
dependencies:
brace-expansion "^1.1.7"
minimatch@^9.0.4:
version "9.0.5"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5"
integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==
minimatch@^9.0.1:
version "9.0.3"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825"
integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==
dependencies:
brace-expansion "^2.0.1"
@@ -2506,11 +2488,6 @@ minipass@^5.0.0:
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c"
integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==
minipass@^7.1.2:
version "7.1.2"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707"
integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
minizlib@^1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d"
@@ -2600,10 +2577,9 @@ mz@^2.7.0:
object-assign "^4.0.1"
thenify-all "^1.0.0"
nan@^2.17.0:
version "2.20.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.20.0.tgz#08c5ea813dd54ed16e5bd6505bf42af4f7838ca3"
integrity sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==
nan@^2.17.0, "nan@github:jkleinsc/nan#remove_accessor_signature":
version "2.16.0"
resolved "https://codeload.github.com/jkleinsc/nan/tar.gz/6a2f95a6a2209d8aa7542fb18099fd808a802059"
napi-build-utils@^1.0.1:
version "1.0.2"
@@ -2634,12 +2610,12 @@ nice-try@^1.0.4:
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
node-abi@^3:
version "3.65.0"
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.65.0.tgz#ca92d559388e1e9cab1680a18c1a18757cdac9d3"
integrity sha512-ThjYBfoDNr08AWx6hGaRbfPwxKV9kVzAzOzlLKbk2CuqXE2xnCh+cbAGnwM3t8Lq4v9rUB7VfondlkBckcJrVA==
node-abi@^2.20.0:
version "2.30.1"
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.30.1.tgz#c437d4b1fe0e285aaf290d45b45d4d7afedac4cf"
integrity sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w==
dependencies:
semver "^7.3.5"
semver "^5.4.1"
node-abi@^3.3.0:
version "3.8.0"
@@ -2663,11 +2639,6 @@ node-addon-api@^4.0.0, node-addon-api@^4.3.0:
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f"
integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==
node-addon-api@^7.1.0:
version "7.1.1"
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558"
integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==
node-fetch-npm@^2.0.2:
version "2.0.4"
resolved "https://registry.yarnpkg.com/node-fetch-npm/-/node-fetch-npm-2.0.4.tgz#6507d0e17a9ec0be3bec516958a497cec54bf5a4"
@@ -2698,12 +2669,12 @@ node-gyp@^10.0.0, node-gyp@^5.0.2, node-gyp@^5.1.0:
tar "^6.1.2"
which "^4.0.0"
node-pty@^1.1.0-beta.14:
version "1.1.0-beta9"
resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.1.0-beta9.tgz#ed643cb3b398d031b4e31c216e8f3b0042435f1d"
integrity sha512-/Ue38pvXJdgRZ3+me1FgfglLd301GhJN0NStiotdt61tm43N5htUyR/IXOUzOKuNaFmCwIhy6nwb77Ky41LMbw==
node-pty@^1.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.0.0.tgz#7daafc0aca1c4ca3de15c61330373af4af5861fd"
integrity sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA==
dependencies:
node-addon-api "^7.1.0"
nan "^2.17.0"
nopt@^4.0.3:
version "4.0.3"
@@ -3125,11 +3096,6 @@ p-try@^2.0.0:
resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
package-json-from-dist@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz#e501cd3094b278495eb4258d4c9f6d5ac3019f00"
integrity sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==
package-json@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed"
@@ -3242,12 +3208,12 @@ path-parse@^1.0.6:
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
path-scurry@^1.11.1:
version "1.11.1"
resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2"
integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==
path-scurry@^1.10.1:
version "1.10.1"
resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.1.tgz#9ba6bf5aa8500fe9fd67df4f0d9483b2b0bfc698"
integrity sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==
dependencies:
lru-cache "^10.2.0"
lru-cache "^9.1.1 || ^10.0.0"
minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
path-type@^2.0.0:
@@ -3636,13 +3602,6 @@ run-queue@^1.0.0, run-queue@^1.0.3:
dependencies:
aproba "^1.1.1"
russh@0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/russh/-/russh-0.0.3.tgz#bcb53d2efbe2b216857171bc5ca2131001ddfa46"
integrity sha512-iTW4M5W856zYjbjQYjlDFaSFSQ6pLBy+zsCYFwhivYuj8U5mZ7kF7TeGOUat9t4l25HVxAS36ivCt5l79p9lcQ==
dependencies:
"@napi-rs/cli" "^2.18.3"
rxjs@^7.5.2, rxjs@^7.5.7:
version "7.5.7"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.7.tgz#2ec0d57fdc89ece220d2e702730ae8f1e49def39"
@@ -3950,7 +3909,6 @@ strict-uri-encode@^2.0.0:
integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY=
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
name string-width-cjs
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -4028,7 +3986,6 @@ stringify-package@^1.0.0, stringify-package@^1.0.1:
integrity sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.1:
name strip-ansi-cjs
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -4475,7 +4432,6 @@ worker-farm@^1.6.0, worker-farm@^1.7.0:
errno "~0.1.7"
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
name wrap-ansi-cjs
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==

View File

@@ -2,8 +2,6 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.automation.apple-events</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>

View File

@@ -51,7 +51,6 @@ mac:
entitlements: "./build/mac/entitlements.plist"
entitlementsInherit: "./build/mac/entitlements.plist"
extendInfo:
ElectronTeamID: ${teamId}
NSRequiresAquaSystemAppearance: false
NSCameraUsageDescription: "A subprocess requests access to the device's camera."
NSMicrophoneUsageDescription: "A subprocess requests access to the device's microphone."
@@ -61,7 +60,6 @@ mac:
NSDownloadsFolderUsageDescription: "A subprocess requests access to the user's Downloads folder."
NSNetworkVolumesUsageDescription: 'A subprocess requests access to files on a network volume.'
NSRemovableVolumesUsageDescription: 'A subprocess requests access to files on a removable volume.'
NSAppleEventsUsageDescription: 'A subprocess requests permission to send AppleScript events to another application.'
linux:
category: "Utility;TerminalEmulator;System"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: Afrikaans\n"
"Language: af_ZA\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
"PO-Revision-Date: 2024-03-11 20:11\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: Bulgarian\n"
"Language: bg_BG\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
"PO-Revision-Date: 2024-03-11 20:11\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: Czech\n"
"Language: cs_CZ\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
"PO-Revision-Date: 2024-03-11 20:11\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"
@@ -1935,7 +1935,7 @@ msgstr "Sériové připojení"
#: tabby-serial/src/profiles.ts:73
msgid "Serial: {description}"
msgstr "Sériový: {description}"
msgstr "Seriální: {description}"
#: locale/tmp-html/tabby-settings/src/components/setVaultPassphraseModal.component.html:2
#: locale/tmp-html/tabby-settings/src/components/vaultSettingsTab.component.html:5

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: Danish\n"
"Language: da_DK\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
"PO-Revision-Date: 2024-03-11 20:11\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"
@@ -100,12 +100,12 @@ msgstr "Tillader hurtigt at åbne en terminal i den valgte mappe"
#: locale/tmp-html/tabby-core/src/components/welcomeTab.component.html:25
#: locale/tmp-html/tabby-terminal/src/components/colorSchemeSettingsTab.component.html:11
msgid "Always dark"
msgstr "Altid mørk"
msgstr ""
#: locale/tmp-html/tabby-core/src/components/welcomeTab.component.html:27
#: locale/tmp-html/tabby-terminal/src/components/colorSchemeSettingsTab.component.html:13
msgid "Always light"
msgstr "Altid lys"
msgstr ""
#: locale/tmp-html/tabby-terminal/src/components/appearanceSettingsTab.component.html:2
#: tabby-terminal/src/settings.ts:14
@@ -272,7 +272,7 @@ msgstr "Ciphers"
#: locale/tmp-html/tabby-terminal/src/components/colorSchemeSelector.component.html:5
msgid "Clear"
msgstr "Ryd"
msgstr ""
#: tabby-core/src/services/profiles.service.ts:248
msgid "Clear recent profiles"
@@ -324,7 +324,7 @@ msgstr "Luk faner til højre"
#: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:182
msgid "Close the window after closing the last tab"
msgstr "Luk vinduet efter lukningen af den sidste fane"
msgstr ""
#: locale/tmp-html/tabby-settings/src/components/editProfileModal.component.html:33
#: tabby-core/src/tabContextMenu.ts:132
@@ -337,16 +337,16 @@ msgstr "Farvetema"
#: locale/tmp-html/tabby-terminal/src/components/colorSchemeSettingsTab.component.html:2
msgid "Color schemes"
msgstr "Farveskemaer"
msgstr ""
#: locale/tmp-html/tabby-serial/src/components/serialProfileSettings.component.html:81
#: locale/tmp-html/tabby-ssh/src/components/sshProfileSettings.component.html:216
msgid "Colors"
msgstr "Farver"
msgstr ""
#: tabby-core/src/hotkeys.ts:72
msgid "Combine all tabs into the current tab"
msgstr "Kombinér alle faner til den aktuelle fane"
msgstr ""
#: locale/tmp-html/tabby-local/src/components/commandLineEditor.component.html:4
msgid "Command line"
@@ -358,15 +358,15 @@ msgstr "Kommandoens stdin/stdout bruges i stedet for en netværksforbindelse"
#: tabby-core/src/services/commands.service.ts:105
msgid "Commands"
msgstr "Kommandoer"
msgstr ""
#: tabby-core/src/theme.ts:16
msgid "Compact (legacy)"
msgstr "Kompakt (ældre)"
msgstr ""
#: tabby-settings/src/components/configSyncSettingsTab.component.ts:126
msgid "Config deleted"
msgstr "Konfig slettet"
msgstr ""
#: tabby-settings/src/components/configSyncSettingsTab.component.ts:108
msgid "Config downloaded"
@@ -428,7 +428,7 @@ msgstr "Kontekstmenu"
#: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:14
msgid "Controls the amount of space between elements"
msgstr "Styrer mængden af mellemrum mellem elementer"
msgstr ""
#: tabby-terminal/src/api/baseTerminalTab.component.ts:240
#: tabby-terminal/src/api/baseTerminalTab.component.ts:248
@@ -449,7 +449,7 @@ msgstr "Kopiér nuværende sti"
#: tabby-electron/src/sftpContextMenu.ts:29
msgid "Copy full path"
msgstr "Kopiér hele stien"
msgstr ""
#: locale/tmp-html/tabby-terminal/src/components/terminalSettingsTab.component.html:97
msgid "Copy on select"
@@ -461,7 +461,7 @@ msgstr "Kopiér til udklipsholder"
#: locale/tmp-html/tabby-terminal/src/components/terminalSettingsTab.component.html:103
msgid "Copy with formatting"
msgstr "Kopiér med formatering"
msgstr ""
#: tabby-core/src/services/config.service.ts:425
msgid "Could not decrypt config"
@@ -469,12 +469,12 @@ msgstr "Kunne ikke dekryptere konfiguration"
#: locale/tmp-html/tabby-ssh/src/components/sftpCreateDirectoryModal.component.html:9
msgid "Create"
msgstr "Opret"
msgstr ""
#: locale/tmp-html/tabby-ssh/src/components/sftpPanel.component.html:7
#: tabby-ssh/src/sftpContextMenu.ts:29
msgid "Create directory"
msgstr "Opret mappe"
msgstr ""
#: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:90
msgid "Current"
@@ -498,7 +498,7 @@ msgstr "Markørens form"
#: locale/tmp-html/tabby-terminal/src/components/colorSchemeSettingsForMode.component.html:46
msgid "Custom"
msgstr "Tilpasset"
msgstr ""
#: locale/tmp-html/tabby-terminal/src/components/appearanceSettingsTab.component.html:90
msgid "Custom CSS"
@@ -506,7 +506,7 @@ msgstr "Brugerdefineret CSS"
#: locale/tmp-html/tabby-terminal/src/components/colorSchemeSettingsTab.component.html:17
msgid "Dark mode"
msgstr "Mørk tilstand"
msgstr ""
#: locale/tmp-html/tabby-serial/src/components/serialProfileSettings.component.html:26
msgid "Data bits"
@@ -518,15 +518,15 @@ msgstr "Fejlfinding"
#: tabby-core/src/hotkeys.ts:256
msgid "Decrease horizontal split size"
msgstr "Reducér den vandrette splitstørrelse"
msgstr ""
#: tabby-core/src/hotkeys.ts:248
msgid "Decrease vertical split size"
msgstr "Reducér den lodrette splitstørrelse"
msgstr ""
#: locale/tmp-html/tabby-settings/src/components/profilesSettingsTab.component.html:92
msgid "Default \"Connect to\" type"
msgstr "Standard \"Forbind til\"-type"
msgstr ""
#: locale/tmp-html/tabby-settings/src/components/profilesSettingsTab.component.html:93
msgid "Default connection type used by quick connect feature (ex. SSH, Telnet)"
@@ -582,7 +582,7 @@ msgstr "Slet {fullPath}?"
#: tabby-terminal/src/hotkeys.ts:42
msgid "Delete entire line"
msgstr "Slet hele linjen"
msgstr ""
#: tabby-terminal/src/hotkeys.ts:46
msgid "Delete next word"
@@ -594,7 +594,7 @@ msgstr "Slet forrige ord"
#: tabby-settings/src/components/configSyncSettingsTab.component.ts:114
msgid "Delete the config on the remote side?"
msgstr "Slet konfigurationen på fjernsiden?"
msgstr ""
#: tabby-settings/src/components/profilesSettingsTab.component.ts:226
msgid "Delete the group's profiles?"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: German\n"
"Language: de_DE\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
"PO-Revision-Date: 2024-03-11 20:11\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: English, United Kingdom\n"
"Language: en_GB\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
"PO-Revision-Date: 2024-03-11 20:11\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: Spanish\n"
"Language: es_ES\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
"PO-Revision-Date: 2024-03-11 20:11\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: French\n"
"Language: fr_FR\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
"PO-Revision-Date: 2024-03-11 20:11\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: Croatian\n"
"Language: hr_HR\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
"PO-Revision-Date: 2024-03-11 20:11\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: Indonesian\n"
"Language: id_ID\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
"PO-Revision-Date: 2024-03-11 20:11\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"
@@ -1762,11 +1762,11 @@ msgstr "Ukuran Semula"
#: tabby-serial/src/hotkeys.ts:14
msgid "Restart current serial session"
msgstr "Mulai ulang sesi serial saat ini"
msgstr ""
#: tabby-ssh/src/hotkeys.ts:10
msgid "Restart current SSH session"
msgstr "Mulai ulang sesi SSH saat ini"
msgstr ""
#: tabby-telnet/src/hotkeys.ts:10
msgid "Restart current Telnet session"
@@ -2401,7 +2401,7 @@ msgstr ""
#: tabby-ssh/src/session/ssh.ts:465
msgid "Using preset password"
msgstr "Gunakan kata sandi yang telah disetel sebelumnya"
msgstr ""
#: locale/tmp-html/tabby-settings/src/components/vaultSettingsTab.component.html:9
msgid "Vault"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: Italian\n"
"Language: it_IT\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
"PO-Revision-Date: 2024-03-11 20:11\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"
@@ -31,7 +31,7 @@ msgstr "Annulla tutto"
#: locale/tmp-html/tabby-ssh/src/components/hostKeyPromptModal.component.html:24
msgid "Accept and remember key"
msgstr "Accetta e ricorda chiave"
msgstr "Accetta e ricorda la chiave"
#: locale/tmp-html/tabby-ssh/src/components/hostKeyPromptModal.component.html:25
msgid "Accept just this once"
@@ -43,7 +43,7 @@ msgstr "Accessibilità"
#: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:27
msgid "Acrylic background"
msgstr "Sfondo acrilico"
msgstr "Sfondo traslucido"
#: locale/tmp-html/tabby-local/src/components/commandLineEditor.component.html:24
#: locale/tmp-html/tabby-local/src/components/environmentEditor.component.html:11
@@ -225,7 +225,8 @@ msgstr "Incolla tra parentesi (richiede una shell che lo supporti)"
#: tabby-terminal/src/services/multifocus.service.ts:19
msgid "Broadcast mode. Click anywhere to cancel."
msgstr "Modalità broadcast. Fai clic ovunque per disabilitare."
msgstr "Modalità broadcast. \n"
"Fai clic ovunque per annullare."
#: locale/tmp-html/tabby-plugin-manager/src/components/pluginsSettingsTab.component.html:50
#: tabby-core/src/services/profiles.service.ts:411
@@ -507,7 +508,7 @@ msgstr "CSS personalizzato"
#: locale/tmp-html/tabby-terminal/src/components/colorSchemeSettingsTab.component.html:17
msgid "Dark mode"
msgstr "Tema scuro"
msgstr "Modalità scura"
#: locale/tmp-html/tabby-serial/src/components/serialProfileSettings.component.html:26
msgid "Data bits"
@@ -707,7 +708,7 @@ msgstr "La selezione con doppio clic si fermerà a questi caratteri"
#: tabby-core/src/tabContextMenu.ts:79
msgid "Down"
msgstr "In basso"
msgstr "Giù"
#: locale/tmp-html/tabby-settings/src/components/configSyncSettingsTab.component.html:43
msgid "Download"
@@ -895,7 +896,7 @@ msgstr "Evidenzia il riquadro a destra"
#: tabby-core/src/theme.ts:34
msgid "Follow the color scheme"
msgstr "Segui il tema di sistema"
msgstr "Segui lo schema dei colori"
#: locale/tmp-html/tabby-terminal/src/components/appearanceSettingsTab.component.html:5
msgid "Font"
@@ -1208,7 +1209,7 @@ msgstr "Scopri come consentire a Tabby di rilevare la cartella di lavoro della s
#: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:76
#: tabby-core/src/tabContextMenu.ts:80
msgid "Left"
msgstr "A sinistra"
msgstr "Sinistra"
#: locale/tmp-html/tabby-terminal/src/components/terminalSettingsTab.component.html:40
msgid "Lets the shell handle Meta key instead of OS"
@@ -1801,7 +1802,7 @@ msgstr "Riusa la sessione per più schede"
#: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:78
#: tabby-core/src/tabContextMenu.ts:78
msgid "Right"
msgstr "A destra"
msgstr "Destra"
#: locale/tmp-html/tabby-terminal/src/components/terminalSettingsTab.component.html:56
msgid "Right click"
@@ -2360,7 +2361,7 @@ msgstr "Sgancia"
#: tabby-core/src/tabContextMenu.ts:81
msgid "Up"
msgstr "In alto"
msgstr "Su"
#: locale/tmp-html/tabby-settings/src/components/settingsTab.component.html:14
#: tabby-electron/src/services/updater.service.ts:133
@@ -2428,7 +2429,7 @@ msgstr "La cassaforte non è configurata"
#: tabby-core/src/services/fileProviders.service.ts:40
msgid "Vault master passphrase needs to be set to allow storing secrets"
msgstr "Per consentire la memorizzazione di dati cifrati deve essere impostata la frase segreta principale della cassaforte"
msgstr "Per consentire la memorizzazione di dati cifrati deve essere impostata la frase segreta principale della cassaforte."
#: locale/tmp-html/tabby-ssh/src/components/sshSettingsTab.component.html:11
msgid "Verify host keys when connecting"
@@ -2464,7 +2465,7 @@ msgstr "Attenzione: la chiave dell'host remoto è improvvisamente cambiata!"
#: locale/tmp-html/tabby-settings/src/components/settingsTab.component.html:67
msgid "We're only tracking your Tabby and OS versions."
msgstr "Monitoreremo soltanto le versioni di Tabby e del Sistema Operativo."
msgstr "Monitoreremo soltanto le versioni di Tabby e del sistema operativo."
#: tabby-core/src/components/welcomeTab.component.ts:25
msgid "Welcome"
@@ -2509,7 +2510,7 @@ msgstr "Dimensione finestra fuori dal bordo"
#: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:52
msgid "Window frame"
msgstr "Cornice della finestra"
msgstr "Cornice finestra"
#: locale/tmp-html/tabby-terminal/src/components/terminalSettingsTab.component.html:165
msgid "Windows"
@@ -2525,7 +2526,7 @@ msgstr "Percorso WinSCP"
#: locale/tmp-html/tabby-terminal/src/components/terminalSettingsTab.component.html:74
msgid "Word separators"
msgstr "Separatori di parole"
msgstr "Separatori parole"
#: locale/tmp-html/tabby-local/src/components/localProfileSettings.component.html:10
msgid "Working directory"
@@ -2562,11 +2563,11 @@ msgstr "In questo momento potresti essere sotto un attacco man-in-the-middle, o
#: tabby-terminal/src/hotkeys.ts:54
msgid "Zoom in"
msgstr "Zoom avanti"
msgstr "Zoom +"
#: tabby-terminal/src/hotkeys.ts:58
msgid "Zoom out"
msgstr "Zoom indietro"
msgstr "Zoom -"
#: locale/tmp-html/tabby-ssh/src/components/sshPortForwardingConfig.component.html:43
#: locale/tmp-html/tabby-ssh/src/components/sshPortForwardingConfig.component.html:5

View File

@@ -10,11 +10,11 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: Japanese\n"
"Language: ja_JP\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
"PO-Revision-Date: 2024-03-11 20:11\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"
msgstr "\"{command}\"が実行中です。閉じてもよろしいですか?"
msgstr "\"{command}\"が実行中です。閉じすか?"
#: tabby-settings/src/components/profilesSettingsTab.component.ts:78
#: tabby-settings/src/components/profilesSettingsTab.component.ts:88
@@ -23,7 +23,7 @@ msgstr "{name} コピー"
#: locale/tmp-html/tabby-terminal/src/components/appearanceSettingsTab.component.html:77
msgid "A second font family used to display characters missing in the main font"
msgstr "メインフォントに不足している文字を表示する際に使用されます"
msgstr "既定フォントに不足している文字を表示する際に使用されます"
#: tabby-core/src/components/transfersMenu.component.ts:49
msgid "Abort all"
@@ -64,7 +64,7 @@ msgstr "追加..."
#: locale/tmp-html/tabby-terminal/src/components/appearanceSettingsTab.component.html:84
msgid "Additional space between lines"
msgstr "行と行の間隔を広げます"
msgstr "行間にスペースを空けます"
#: locale/tmp-html/tabby-serial/src/components/serialProfileSettings.component.html:22
#: locale/tmp-html/tabby-settings/src/components/configSyncSettingsTab.component.html:61
@@ -79,15 +79,15 @@ msgstr "SSHエージェント"
#: locale/tmp-html/tabby-ssh/src/components/sshProfileSettings.component.html:134
msgid "Agent forwarding"
msgstr "SSHエージェント転送"
msgstr "SSHエージェント転送"
#: locale/tmp-html/tabby-ssh/src/components/sshSettingsTab.component.html:35
msgid "Agent pipe path"
msgstr "SSHエージェントのパイプ"
msgstr "SSHエージェントのパイプのパス"
#: locale/tmp-html/tabby-ssh/src/components/sshSettingsTab.component.html:24
msgid "Agent type"
msgstr "SSHエージェントの指定"
msgstr "SSHエージェントの種類"
#: locale/tmp-html/tabby-terminal/src/components/terminalSettingsTab.component.html:169
msgid "Allows opening .bat files in tabs, but breaks some shells"
@@ -354,7 +354,7 @@ msgstr "コマンドライン"
#: locale/tmp-html/tabby-ssh/src/components/sshProfileSettings.component.html:13
msgid "Command's stdin/stdout is used instead of a network connection"
msgstr "ネットワーク接続の代わりにコマンドの stdin/stdout 使用ます"
msgstr "ネットワーク接続の代わりにコマンドの stdin/stdout 使用されます"
#: tabby-core/src/services/commands.service.ts:105
msgid "Commands"
@@ -486,7 +486,7 @@ msgstr "使用中の配色"
#: locale/tmp-html/tabby-ssh/src/components/hostKeyPromptModal.component.html:17
msgid "Current host key fingerprint"
msgstr "現在のホストのフィンガープリント"
msgstr "使用中のホストキーのフィンガープリント"
#: tabby-core/src/tabContextMenu.ts:184
msgid "Current process: {name}"
@@ -618,7 +618,7 @@ msgstr "デバイス"
#: locale/tmp-html/tabby-ssh/src/components/sshProfileSettings.component.html:10
msgid "Direct"
msgstr "直接接続"
msgstr "直接"
#: locale/tmp-html/tabby-plugin-manager/src/components/pluginsSettingsTab.component.html:57
msgid "Disable"
@@ -1039,11 +1039,11 @@ msgstr "ホスト"
#: locale/tmp-html/tabby-ssh/src/components/sshProfileSettings.component.html:206
msgid "Host key"
msgstr "ホスト"
msgstr "ホストキー"
#: locale/tmp-html/tabby-ssh/src/components/hostKeyPromptModal.component.html:3
msgid "Host key verification"
msgstr "ホストの検証"
msgstr "ホストキーの検証"
#: locale/tmp-html/tabby-settings/src/components/hotkeySettingsTab.component.html:2
#: tabby-settings/src/settings.ts:15
@@ -1131,7 +1131,7 @@ msgstr "無効な構文"
#: locale/tmp-html/tabby-ssh/src/components/sshProfileSettings.component.html:16
#: locale/tmp-html/tabby-ssh/src/components/sshProfileSettings.component.html:43
msgid "Jump host"
msgstr "踏み台サーバー"
msgstr "ジャンプサーバー"
#: tabby-terminal/src/hotkeys.ts:34
msgid "Jump to next word"
@@ -1188,7 +1188,7 @@ msgstr "言語"
#: locale/tmp-html/tabby-ssh/src/components/hostKeyPromptModal.component.html:11
msgid "Last known host key fingerprint"
msgstr "最後に使われたホストのフィンガープリント"
msgstr "最後に使用したホストキーのフィンガープリント"
#: tabby-ssh/src/tabContextMenu.ts:32
msgid "Launch WinSCP"
@@ -1201,7 +1201,7 @@ msgstr "現在のSSHセッションでWinSCPを起動"
#: locale/tmp-html/tabby-ssh/src/components/sftpPanel.component.html:16
#: locale/tmp-html/tabby-ssh/src/components/sshTab.component.html:7
msgid "Learn how to allow Tabby to detect remote shell's working directory."
msgstr "リモートの作業ディレクトリを自動検出する方法をご紹介します。"
msgstr "Tabbyにリモートの作業ディレクトリを検出させる方法をご紹介します。"
#: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:134
#: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:76
@@ -1227,7 +1227,7 @@ msgstr "Enterキーを押すと入力が送信されます"
#: locale/tmp-html/tabby-terminal/src/components/appearanceSettingsTab.component.html:83
msgid "Line padding"
msgstr "行間"
msgstr "行間の大きさ"
#: locale/tmp-html/tabby-ssh/src/components/sftpPanel.component.html:21
msgid "Loading"
@@ -1325,7 +1325,7 @@ msgstr "新規"
#: tabby-local/src/tabContextMenu.ts:53
msgid "New admin tab"
msgstr "新しい管理者権限タブ"
msgstr "新しい管理者タブ"
#: tabby-settings/src/components/configSyncSettingsTab.component.ts:58
msgid "New config on {platform}"
@@ -1511,11 +1511,11 @@ msgstr "リモートを上書きして同期"
#: tabby-settings/src/components/configSyncSettingsTab.component.ts:76
msgid "Overwrite the config on the remote side and start syncing?"
msgstr "リモート上の設定を上書きして同期を開始しますか?"
msgstr "リモート上の設定を上書きして同期を開始しますか?"
#: tabby-settings/src/components/configSyncSettingsTab.component.ts:96
msgid "Overwrite the local config and start syncing?"
msgstr "現在のローカル上の設定を上書きして、同期を開始しますか?"
msgstr "これまでのローカル上の設定を上書きして、同期を開始しますか?"
#: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:189
msgid "Pane resize step"
@@ -1535,7 +1535,7 @@ msgstr "パリティ"
#: locale/tmp-html/tabby-settings/src/components/configSyncSettingsTab.component.html:64
msgid "Partial config sync is not possible when the config is encrypted via Vault."
msgstr "Vaultによって設定ファイルを暗号化した場合、設定の一部だけを同期から外すことはできません。"
msgstr "設定ファイルがVaultによって暗号化されている場合、部分的な設定の同期はできません。"
#: tabby-terminal/src/components/inputProcessingSettings.component.ts:17
msgid "Pass-through"
@@ -1562,11 +1562,11 @@ msgstr "クリップボードから貼り付け"
#: locale/tmp-html/tabby-terminal/src/components/terminalSettingsTab.component.html:63
msgid "Paste if no selection, else copy"
msgstr "通常は貼り付け、テキスト選択時のみコピー"
msgstr "通常は貼り付け、テキスト選択時のみコピー"
#: tabby-terminal/src/api/baseTerminalTab.component.ts:528
msgid "Paste multiple lines?"
msgstr "複数行貼り付けを実行してもよろしいですか?"
msgstr "複数行貼り付けを実行してもよろしいですか?"
#: locale/tmp-html/tabby-terminal/src/components/terminalSettingsTab.component.html:68
msgid "Paste on middle-click"
@@ -1960,7 +1960,7 @@ msgstr "最近使用したプロファイルをセレクターに表示しない
#: locale/tmp-html/tabby-ssh/src/components/sshSettingsTab.component.html:36
msgid "Sets the SSH agent's named pipe path."
msgstr "SSHエージェントの名前付きパイプのパスを指定します。"
msgstr "SSHエージェントの名前付きパイプのパスを設定"
#: tabby-settings/src/buttonProvider.ts:28
#: tabby-settings/src/components/settingsTab.component.ts:57
@@ -1990,7 +1990,7 @@ msgstr "{type} プロファイルセレクターを表示"
#: locale/tmp-html/tabby-terminal/src/components/terminalSettingsTab.component.html:117
msgid "Show a confirmation box when pasting multiple lines"
msgstr "複数行貼り付けをする際に確認画面を表示します"
msgstr "複数行貼り付ける際に確認ボックスを表示します"
#: locale/tmp-html/tabby-settings/src/components/profilesSettingsTab.component.html:75
msgid "Show built-in profiles in selector"
@@ -2268,7 +2268,7 @@ msgstr "端末の識別情報"
#: locale/tmp-html/tabby-core/src/components/welcomeTab.component.html:7
msgid "Thank you for downloading Tabby!"
msgstr "Tabbyをダウンロードしてくださり、ありがとうございます!"
msgstr "Tabbyをダウンロードしていただきありがとうございます!"
#: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:5
msgid "Theme"
@@ -2280,7 +2280,7 @@ msgstr "ファイル転送を使用中"
#: locale/tmp-html/tabby-ssh/src/components/sshProfileSettings.component.html:102
msgid "There is a saved password for this connection"
msgstr "この接続に使用するパスワード保存ています。"
msgstr "この接続に利用可能なパスワード保存されています。"
#: locale/tmp-html/tabby-settings/src/components/profilesSettingsTab.component.html:102
msgid "These apply to all profiles of a given type"
@@ -2397,7 +2397,7 @@ msgstr "ユーザー名"
#: locale/tmp-html/tabby-ssh/src/components/sshProfileSettings.component.html:25
msgid "Using CONNECT method"
msgstr "CONNECTメソッド使用します"
msgstr "CONNECTメソッド使用"
#: tabby-ssh/src/session/ssh.ts:465
msgid "Using preset password"
@@ -2426,11 +2426,11 @@ msgstr "Vaultが設定されていません"
#: tabby-core/src/services/fileProviders.service.ts:40
msgid "Vault master passphrase needs to be set to allow storing secrets"
msgstr "機密性の高い情報を保存するには、Vaultマスターパスフレーズを設定する必要があります"
msgstr "機密性の高い情報を保存するには、Vaultマスターパスフレーズを設定する必要があります"
#: locale/tmp-html/tabby-ssh/src/components/sshSettingsTab.component.html:11
msgid "Verify host keys when connecting"
msgstr "接続時にホスト鍵を検証"
msgstr "接続時にホストキーを確認"
#: locale/tmp-html/tabby-plugin-manager/src/components/pluginsSettingsTab.component.html:75
msgid "Version"
@@ -2478,7 +2478,7 @@ msgstr "セッション終了時"
#: locale/tmp-html/tabby-terminal/src/components/terminalSettingsTab.component.html:82
msgid "When enabled, links are only clickable while holding this key"
msgstr "有効にすると、特定のキーを押しながらクリックした時のみリンクを開きます"
msgstr "有効にすると、特定のキーを押しながらクリックした時のみリンクが開けるようになります"
#: locale/tmp-html/tabby-ssh/src/components/sshSettingsTab.component.html:18
msgid "When WinSCP is detected, you can launch an SCP session from the context menu."
@@ -2556,7 +2556,7 @@ msgstr "後から変更できますが、忘れた場合復元することはで
#: locale/tmp-html/tabby-ssh/src/components/hostKeyPromptModal.component.html:7
msgid "You could be under a man-in-the-middle attack right now, or the host key could have just been changed."
msgstr "現在中間者攻撃を受けているか、もしくはホストが変更された可能性があります。"
msgstr "現在中間者攻撃を受けているか、もしくはホストキーが変更された可能性があります。"
#: tabby-terminal/src/hotkeys.ts:54
msgid "Zoom in"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: Korean\n"
"Language: ko_KR\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
"PO-Revision-Date: 2024-03-11 20:11\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: Polish\n"
"Language: pl_PL\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
"PO-Revision-Date: 2024-03-11 20:11\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: Portuguese, Brazilian\n"
"Language: pt_BR\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
"PO-Revision-Date: 2024-03-11 20:11\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: Portuguese\n"
"Language: pt_PT\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
"PO-Revision-Date: 2024-03-11 20:11\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"
@@ -39,7 +39,7 @@ msgstr "Aceitar apenas desta vez"
#: locale/tmp-html/tabby-settings/src/components/settingsTab.component.html:84
msgid "Accessibility"
msgstr "acessibilidade"
msgstr ""
#: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:27
msgid "Acrylic background"
@@ -100,12 +100,12 @@ msgstr "Permite abrir um terminal rapidamente na pasta selecionada"
#: locale/tmp-html/tabby-core/src/components/welcomeTab.component.html:25
#: locale/tmp-html/tabby-terminal/src/components/colorSchemeSettingsTab.component.html:11
msgid "Always dark"
msgstr "sempre escuro"
msgstr ""
#: locale/tmp-html/tabby-core/src/components/welcomeTab.component.html:27
#: locale/tmp-html/tabby-terminal/src/components/colorSchemeSettingsTab.component.html:13
msgid "Always light"
msgstr "Sempre luminoso"
msgstr ""
#: locale/tmp-html/tabby-terminal/src/components/appearanceSettingsTab.component.html:2
#: tabby-terminal/src/settings.ts:14
@@ -498,7 +498,7 @@ msgstr "Formato do cursor"
#: locale/tmp-html/tabby-terminal/src/components/colorSchemeSettingsForMode.component.html:46
msgid "Custom"
msgstr "Personalizado"
msgstr ""
#: locale/tmp-html/tabby-terminal/src/components/appearanceSettingsTab.component.html:90
msgid "Custom CSS"
@@ -538,7 +538,7 @@ msgstr "Perfil padrão para novas abas"
#: locale/tmp-html/tabby-settings/src/components/editProfileGroupModal.component.html:16
msgid "Default profile group settings"
msgstr "Parâmetros do grupo de perfis por defeito"
msgstr ""
#: locale/tmp-html/tabby-settings/src/components/profilesSettingsTab.component.html:101
msgid "Default profile settings"
@@ -736,7 +736,7 @@ msgstr "Editar"
#: tabby-electron/src/sftpContextMenu.ts:35
msgid "Edit locally"
msgstr "Editar localmente"
msgstr ""
#: locale/tmp-html/tabby-plugin-manager/src/components/pluginsSettingsTab.component.html:58
msgid "Enable"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: Russian\n"
"Language: ru_RU\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
"PO-Revision-Date: 2024-03-11 20:11\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: Serbian (Cyrillic)\n"
"Language: sr_SP\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
"PO-Revision-Date: 2024-03-11 20:11\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: Swedish\n"
"Language: sv_SE\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
"PO-Revision-Date: 2024-03-11 20:11\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"
@@ -35,7 +35,7 @@ msgstr "Acceptera och kom ihåg nyckel"
#: locale/tmp-html/tabby-ssh/src/components/hostKeyPromptModal.component.html:25
msgid "Accept just this once"
msgstr "Acceptera bara den här gången"
msgstr "Acceptera bara denna gång"
#: locale/tmp-html/tabby-settings/src/components/settingsTab.component.html:84
msgid "Accessibility"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: Turkish\n"
"Language: tr_TR\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
"PO-Revision-Date: 2024-03-11 20:11\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: Ukrainian\n"
"Language: uk_UA\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
"PO-Revision-Date: 2024-03-11 20:11\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: Chinese Simplified\n"
"Language: zh_CN\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
"PO-Revision-Date: 2024-03-11 20:11\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: Chinese Traditional\n"
"Language: zh_TW\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
"PO-Revision-Date: 2024-03-11 20:11\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"

View File

@@ -39,7 +39,7 @@
"cross-env": "7.0.3",
"css-loader": "^6.7.3",
"deep-equal": "2.0.5",
"electron": "^29",
"electron": "^27.0.4",
"electron-builder": "^24.6.4",
"electron-download": "^4.1.1",
"electron-installer-snap": "^5.1.0",
@@ -53,9 +53,9 @@
"html-loader": "4.2.0",
"json-loader": "^0.5.7",
"lru-cache": "^6.0.0",
"macos-release": "^3.3.0",
"macos-release": "^3.1.0",
"ngx-toastr": "^16.0.2",
"node-abi": "^3.65.0",
"node-abi": "^3.51.0",
"npmlog": "6.0.2",
"npx": "^10.2.2",
"patch-package": "^6.4.7",
@@ -76,6 +76,7 @@
"source-code-pro": "^2.38.0",
"source-map-loader": "^4.0.1",
"source-sans-pro": "3.6.0",
"ssh2": "^1.14.0",
"style-loader": "^3.3.1",
"svg-inline-loader": "^0.8.2",
"thenby": "^1.3.4",

39
patches/ssh2+1.11.0.patch Normal file
View File

@@ -0,0 +1,39 @@
diff --git a/node_modules/ssh2/lib/protocol/keyParser.js b/node_modules/ssh2/lib/protocol/keyParser.js
index 9860e3f..ee82e51 100644
--- a/node_modules/ssh2/lib/protocol/keyParser.js
+++ b/node_modules/ssh2/lib/protocol/keyParser.js
@@ -15,6 +15,7 @@ const {
sign: sign_,
verify: verify_,
} = require('crypto');
+const { createVerify: createVerifyDSS } = require('browserify-sign')
const supportedOpenSSLCiphers = getCiphers();
const { Ber } = require('asn1');
@@ -404,6 +405,17 @@ const BaseKey = {
return new Error('No public key available');
if (!algo || typeof algo !== 'string')
algo = this[SYM_HASH_ALGO];
+
+ if (algo === 'dss1') {
+ const verifier = createVerifyDSS('DSA-SHA1');
+ verifier.update(data);
+ try {
+ return verifier.verify(pem, signature);
+ } catch (ex) {
+ return ex;
+ }
+ }
+
try {
return verify_(algo, data, pem, signature);
} catch (ex) {
@@ -1343,7 +1355,7 @@ function parseDER(data, baseType, comment, fullType) {
return new Error('Malformed OpenSSH public key');
pubPEM = genOpenSSLDSAPub(p, q, g, y);
pubSSH = genOpenSSHDSAPub(p, q, g, y);
- algo = 'sha1';
+ algo = 'dss1';
break;
}
case 'ssh-ed25519': {

View File

@@ -24,7 +24,6 @@ builder({
config: {
extraMetadata: {
version: vars.version,
teamId: process.env.APPLE_TEAM_ID,
},
mac: {
identity: !process.env.CI || process.env.CSC_LINK ? undefined : null,

View File

@@ -3,7 +3,7 @@ import * as fs from 'fs'
import * as semver from 'semver'
import * as childProcess from 'child_process'
process.env.ARCH = ((process.env.ARCH || process.arch) === 'arm') ? 'armv7l' : (process.env.ARCH || process.arch)
process.env.ARCH = ((process.env.ARCH || process.arch) === 'arm') ? 'armv7l' : process.env.ARCH || process.arch
import * as url from 'url'
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))

View File

@@ -10,7 +10,7 @@ export { Theme } from './theme'
export { TabContextMenuItemProvider } from './tabContextMenuProvider'
export { SelectorOption } from './selector'
export { CLIHandler, CLIEvent } from './cli'
export { PlatformService, ClipboardContent, MessageBoxResult, MessageBoxOptions, FileDownload, FileUpload, FileTransfer, HTMLFileUpload, FileUploadOptions, DirectoryUpload } from './platform'
export { PlatformService, ClipboardContent, MessageBoxResult, MessageBoxOptions, FileDownload, FileUpload, FileTransfer, HTMLFileUpload, FileUploadOptions } from './platform'
export { MenuItemOptions } from './menu'
export { BootstrapData, PluginInfo, BOOTSTRAP_DATA } from './mainProcess'
export { HostWindowService } from './hostWindow'

View File

@@ -63,24 +63,22 @@ export abstract class FileTransfer {
}
export abstract class FileDownload extends FileTransfer {
abstract write (buffer: Uint8Array): Promise<void>
abstract write (buffer: Buffer): Promise<void>
}
export abstract class FileUpload extends FileTransfer {
abstract read (): Promise<Uint8Array>
abstract read (): Promise<Buffer>
async readAll (): Promise<Uint8Array> {
const result = new Uint8Array(this.getSize())
let pos = 0
async readAll (): Promise<Buffer> {
const buffers: Buffer[] = []
while (true) {
const buf = await this.read()
if (!buf.length) {
break
}
result.set(buf, pos)
pos += buf.length
buffers.push(Buffer.from(buf))
}
return result
return Buffer.concat(buffers)
}
}
@@ -88,26 +86,6 @@ export interface FileUploadOptions {
multiple: boolean
}
export class DirectoryUpload {
private childrens: (FileUpload|DirectoryUpload)[] = []
constructor (private name = '') {
// Just set name for now.
}
getName (): string {
return this.name
}
getChildrens (): (FileUpload|DirectoryUpload)[] {
return this.childrens
}
pushChildren (item: FileUpload|DirectoryUpload): void {
this.childrens.push(item)
}
}
export type PlatformTheme = 'light'|'dark'
export abstract class PlatformService {
@@ -128,54 +106,23 @@ export abstract class PlatformService {
abstract startDownload (name: string, mode: number, size: number): Promise<FileDownload|null>
abstract startUpload (options?: FileUploadOptions): Promise<FileUpload[]>
abstract startUploadDirectory (paths?: string[]): Promise<DirectoryUpload>
async startUploadFromDragEvent (event: DragEvent, multiple = false): Promise<DirectoryUpload> {
const result = new DirectoryUpload()
startUploadFromDragEvent (event: DragEvent, multiple = false): FileUpload[] {
const result: FileUpload[] = []
if (!event.dataTransfer) {
return Promise.resolve(result)
return []
}
const traverseFileTree = (item: any, root: DirectoryUpload = result): Promise<void> => {
return new Promise((resolve) => {
if (item.isFile) {
item.file((file: File) => {
const transfer = new HTMLFileUpload(file)
this.fileTransferStarted.next(transfer)
root.pushChildren(transfer)
resolve()
})
} else if (item.isDirectory) {
const dirReader = item.createReader()
const childrenFolder = new DirectoryUpload(item.name)
dirReader.readEntries(async (entries: any[]) => {
for (const entry of entries) {
await traverseFileTree(entry, childrenFolder)
}
resolve()
})
root.pushChildren(childrenFolder)
} else {
resolve()
}
})
}
const promises: Promise<void>[] = []
const items = event.dataTransfer.items
// eslint-disable-next-line @typescript-eslint/prefer-for-of
for (let i = 0; i < items.length; i++) {
const item = items[i].webkitGetAsEntry()
if (item) {
promises.push(traverseFileTree(item))
if (!multiple) {
break
}
for (let i = 0; i < event.dataTransfer.files.length; i++) {
const file = event.dataTransfer.files[i]
const transfer = new HTMLFileUpload(file)
this.fileTransferStarted.next(transfer)
result.push(transfer)
if (!multiple) {
break
}
}
return Promise.all(promises).then(() => result)
return result
}
getConfigPath (): string|null {
@@ -263,12 +210,12 @@ export class HTMLFileUpload extends FileUpload {
return this.file.size
}
async read (): Promise<Uint8Array> {
async read (): Promise<Buffer> {
const result: any = await this.reader.read()
if (result.done || !result.value) {
return new Uint8Array(0)
return Buffer.from('')
}
const chunk = new Uint8Array(result.value)
const chunk = Buffer.from(result.value)
this.increaseProgress(chunk.length)
return chunk
}

View File

@@ -1,4 +1,4 @@
import { Observable, Subject, takeWhile } from 'rxjs'
import { Observable, Subject } from 'rxjs'
import { Component, Injectable, ViewChild, ViewContainerRef, EmbeddedViewRef, AfterViewInit, OnDestroy, Injector } from '@angular/core'
import { BaseTabComponent, BaseTabProcess, GetRecoveryTokenOptions } from './baseTab.component'
import { TabRecoveryProvider, RecoveryToken } from '../api/tabRecovery'
@@ -381,9 +381,6 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
}
}
}, 100)
// Propagate visibility to new children
this.emitVisibility(this.visibility.value)
}
this.initialized.next()
this.initialized.complete()
@@ -821,13 +818,7 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
if (this.disableDynamicTitle) {
return
}
const titles = [
this.getFocusedTab()?.title,
...this.getAllTabs()
.filter(x => x !== this.getFocusedTab())
.map(x => x.title),
]
this.setTitle([...new Set(titles)].join(' | '))
this.setTitle([...new Set(this.getAllTabs().map(x => x.title))].join(' | '))
}
private attachTabView (tab: BaseTabComponent) {
@@ -843,42 +834,20 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
})
}
tab.subscribeUntilDestroyed(
this.observeUntilChildDetached(tab, tab.focused$),
() => this.updateTitle(),
)
tab.subscribeUntilDestroyed(
this.observeUntilChildDetached(tab, tab.titleChange$),
() => this.updateTitle(),
)
tab.subscribeUntilDestroyed(
this.observeUntilChildDetached(tab, tab.activity$),
a => a ? this.displayActivity() : this.clearActivity(),
)
tab.subscribeUntilDestroyed(
this.observeUntilChildDetached(tab, tab.progress$),
p => this.setProgress(p),
)
tab.subscribeUntilDestroyed(tab.titleChange$, () => this.updateTitle())
tab.subscribeUntilDestroyed(tab.activity$, a => a ? this.displayActivity() : this.clearActivity())
tab.subscribeUntilDestroyed(tab.progress$, p => this.setProgress(p))
if (tab.title) {
this.updateTitle()
}
tab.subscribeUntilDestroyed(
this.observeUntilChildDetached(tab, tab.recoveryStateChangedHint$),
() => {
this.recoveryStateChangedHint.next()
},
)
tab.subscribeUntilDestroyed(tab.recoveryStateChangedHint$, () => {
this.recoveryStateChangedHint.next()
})
tab.destroyed$.subscribe(() => {
this.removeTab(tab)
})
}
private observeUntilChildDetached<T> (tab: BaseTabComponent, event: Observable<T>): Observable<T> {
return event.pipe(takeWhile(() => {
return this.getAllTabs().includes(tab)
}))
}
private onAfterTabAdded (tab: BaseTabComponent) {
setImmediate(() => {
this.layout()

View File

@@ -43,7 +43,6 @@ enableWelcomeTab: true
electronFlags:
- ['force_discrete_gpu', '0']
enableAutomaticUpdates: true
hideTray: false
version: 1
vault: null
encrypted: false

View File

@@ -1,5 +1,5 @@
import { Directive, Output, ElementRef, EventEmitter, AfterViewInit } from '@angular/core'
import { DirectoryUpload, PlatformService } from '../api/platform'
import { FileUpload, PlatformService } from '../api/platform'
import './dropZone.directive.scss'
/** @hidden */
@@ -7,7 +7,7 @@ import './dropZone.directive.scss'
selector: '[dropZone]',
})
export class DropZoneDirective implements AfterViewInit {
@Output() transfer = new EventEmitter<DirectoryUpload>()
@Output() transfer = new EventEmitter<FileUpload>()
private dropHint?: HTMLElement
constructor (
@@ -27,9 +27,11 @@ export class DropZoneDirective implements AfterViewInit {
})
}
})
this.el.nativeElement.addEventListener('drop', async (event: DragEvent) => {
this.el.nativeElement.addEventListener('drop', (event: DragEvent) => {
this.removeHint()
this.transfer.emit(await this.platform.startUploadFromDragEvent(event, true))
for (const transfer of this.platform.startUploadFromDragEvent(event, true)) {
this.transfer.emit(transfer)
}
})
this.el.nativeElement.addEventListener('dragleave', () => {
this.removeHint()

View File

@@ -27,8 +27,12 @@ export class HomeBaseService {
this.platform.openExternal('https://github.com/Eugeny/tabby')
}
openDiscord (): void {
this.platform.openExternal('https://discord.gg/4c5EVTBhtp')
openDiscussions (): void {
this.platform.openExternal('https://github.com/Eugeny/tabby/discussions')
}
openTwitter (): void {
this.platform.openExternal('https://twitter.com/eugeeeeny')
}
openTranslations (): void {

View File

@@ -306,7 +306,7 @@ export class VaultFileProvider extends FileProvider {
id,
description: `${description} (${transfer.getName()})`,
},
value: Buffer.from(await transfer.readAll()).toString('base64'),
value: (await transfer.readAll()).toString('base64'),
})
return `${this.prefix}${id}`
}

View File

@@ -16,6 +16,7 @@ export class ElectronService {
clipboard: Clipboard
globalShortcut: GlobalShortcut
screen: Screen
remote = remote
process: any
autoUpdater: AutoUpdater
powerSaveBlocker: PowerSaveBlocker
@@ -43,6 +44,7 @@ export class ElectronService {
this.BrowserWindow = remote.BrowserWindow
this.Menu = remote.Menu
this.MenuItem = remote.MenuItem
this.MenuItem = remote.MenuItem
this.nativeTheme = remote.nativeTheme
}
}

View File

@@ -5,7 +5,7 @@ import * as os from 'os'
import promiseIpc, { RendererProcessType } from 'electron-promise-ipc'
import { execFile } from 'mz/child_process'
import { Injectable, NgZone } from '@angular/core'
import { PlatformService, ClipboardContent, Platform, MenuItemOptions, MessageBoxOptions, MessageBoxResult, DirectoryUpload, FileUpload, FileDownload, FileUploadOptions, wrapPromise, TranslateService } from 'tabby-core'
import { PlatformService, ClipboardContent, Platform, MenuItemOptions, MessageBoxOptions, MessageBoxResult, FileUpload, FileDownload, FileUploadOptions, wrapPromise, TranslateService } from 'tabby-core'
import { ElectronService } from '../services/electron.service'
import { ElectronHostWindow } from './hostWindow.service'
import { ShellIntegrationService } from './shellIntegration.service'
@@ -48,21 +48,6 @@ export class ElectronPlatformService extends PlatformService {
})
}
async getAllFiles (dir: string, root: DirectoryUpload): Promise<DirectoryUpload> {
const items = await fs.readdir(dir, { withFileTypes: true })
for (const item of items) {
if (item.isDirectory()) {
root.pushChildren(await this.getAllFiles(path.join(dir, item.name), new DirectoryUpload(item.name)))
} else {
const file = new ElectronFileUpload(path.join(dir, item.name), this.electron)
root.pushChildren(file)
await wrapPromise(this.zone, file.open())
this.fileTransferStarted.next(file)
}
}
return root
}
readClipboard (): string {
return this.electron.clipboard.readText()
}
@@ -231,28 +216,6 @@ export class ElectronPlatformService extends PlatformService {
}))
}
async startUploadDirectory (paths?: string[]): Promise<DirectoryUpload> {
const properties: any[] = ['openFile', 'treatPackageAsDirectory', 'openDirectory']
if (!paths) {
const result = await this.electron.dialog.showOpenDialog(
this.hostWindow.getWindow(),
{
buttonLabel: this.translate.instant('Select'),
properties,
},
)
if (result.canceled) {
return new DirectoryUpload()
}
paths = result.filePaths
}
const root = new DirectoryUpload()
root.pushChildren(await this.getAllFiles(paths[0].split(path.sep).join(path.posix.sep), new DirectoryUpload(path.basename(paths[0]))))
return root
}
async startDownload (name: string, mode: number, size: number, filePath?: string): Promise<FileDownload|null> {
if (!filePath) {
const result = await this.electron.dialog.showSaveDialog(
@@ -300,12 +263,12 @@ class ElectronFileUpload extends FileUpload {
private size: number
private mode: number
private file: fs.FileHandle
private buffer: Uint8Array
private buffer: Buffer
private powerSaveBlocker = 0
constructor (private filePath: string, private electron: ElectronService) {
super()
this.buffer = new Uint8Array(256 * 1024)
this.buffer = Buffer.alloc(256 * 1024)
this.powerSaveBlocker = electron.powerSaveBlocker.start('prevent-app-suspension')
}
@@ -328,7 +291,7 @@ class ElectronFileUpload extends FileUpload {
return this.size
}
async read (): Promise<Uint8Array> {
async read (): Promise<Buffer> {
const result = await this.file.read(this.buffer, 0, this.buffer.length, null)
this.increaseProgress(result.bytesRead)
return this.buffer.slice(0, result.bytesRead)
@@ -370,7 +333,7 @@ class ElectronFileDownload extends FileDownload {
return this.size
}
async write (buffer: Uint8Array): Promise<void> {
async write (buffer: Buffer): Promise<void> {
let pos = 0
while (pos < buffer.length) {
const result = await this.file.write(buffer, pos, buffer.length - pos, null)

View File

@@ -1,3 +1,4 @@
import type { AppUpdater } from 'electron-updater'
import { Injectable } from '@angular/core'
import axios from 'axios'
@@ -12,6 +13,7 @@ export class ElectronUpdaterService extends UpdaterService {
private downloaded: Promise<boolean>
private electronUpdaterAvailable = true
private updateURL: string
private autoUpdater: AppUpdater
constructor (
log: LogService,
@@ -28,28 +30,32 @@ export class ElectronUpdaterService extends UpdaterService {
return
}
this.electron.ipcRenderer.on('updater:update-available', () => {
this.autoUpdater = electron.remote.require('electron-updater').autoUpdater
this.autoUpdater.autoDownload = true
this.autoUpdater.autoInstallOnAppQuit = false
this.autoUpdater.on('update-available', () => {
this.logger.info('Update available')
})
this.electron.ipcRenderer.on('updater:update-not-available', () => {
this.autoUpdater.on('update-not-available', () => {
this.logger.info('No updates')
})
this.electron.ipcRenderer.on('updater:error', err => {
this.autoUpdater.on('error', err => {
this.logger.error(err)
this.electronUpdaterAvailable = false
})
this.downloaded = new Promise<boolean>(resolve => {
this.electron.ipcRenderer.once('updater:update-downloaded', () => resolve(true))
this.autoUpdater.once('update-downloaded', () => resolve(true))
})
config.ready$.toPromise().then(() => {
if (config.store.enableAutomaticUpdates && this.electronUpdaterAvailable && !process.env.TABBY_DEV) {
this.logger.debug('Checking for updates')
try {
this.electron.ipcRenderer.send('updater:check-for-updates')
this.autoUpdater.checkForUpdates()
} catch (e) {
this.electronUpdaterAvailable = false
this.logger.info('Electron updater unavailable, falling back', e)
@@ -76,26 +82,26 @@ export class ElectronUpdaterService extends UpdaterService {
reject(err)
}
cancel = () => {
this.electron.ipcRenderer.off('updater:error', onError)
this.electron.ipcRenderer.off('updater:update-not-available', onNoUpdate)
this.electron.ipcRenderer.off('updater:update-available', onUpdate)
this.autoUpdater.off('error', onError)
this.autoUpdater.off('update-not-available', onNoUpdate)
this.autoUpdater.off('update-available', onUpdate)
}
this.electron.ipcRenderer.on('updater:error', onError)
this.electron.ipcRenderer.on('updater:update-not-available', onNoUpdate)
this.electron.ipcRenderer.on('updater:update-available', onUpdate)
this.autoUpdater.on('error', onError)
this.autoUpdater.on('update-not-available', onNoUpdate)
this.autoUpdater.on('update-available', onUpdate)
try {
this.electron.ipcRenderer.send('updater:check-for-updates')
this.autoUpdater.checkForUpdates()
} catch (e) {
this.electronUpdaterAvailable = false
this.logger.info('Electron updater unavailable, falling back', e)
}
})
this.electron.ipcRenderer.on('updater:update-available', () => {
this.autoUpdater.on('update-available', () => {
this.logger.info('Update available')
})
this.electron.ipcRenderer.once('updater:update-not-available', () => {
this.autoUpdater.once('update-not-available', () => {
this.logger.info('No updates')
})
@@ -132,7 +138,7 @@ export class ElectronUpdaterService extends UpdaterService {
},
)).response === 0) {
await this.downloaded
this.electron.ipcRenderer.send('updater:quit-and-install')
this.autoUpdater.quitAndInstall()
}
}
}

View File

@@ -49,24 +49,19 @@ export class EditSFTPContextMenu extends SFTPContextMenuItemProvider {
this.platform.openPath(tempPath)
const events = new Subject<string>()
fs.chmodSync(tempPath, 0o700)
// skip the first burst of events
setTimeout(() => {
const watcher = fs.watch(tempPath, event => events.next(event))
events.pipe(debounceTime(1000), debounce(async event => {
if (event === 'rename') {
watcher.close()
}
const upload = await this.platform.startUpload({ multiple: false }, [tempPath])
if (!upload.length) {
return
}
await sftp.upload(item.fullPath, upload[0])
await sftp.chmod(item.fullPath, item.mode)
})).subscribe()
watcher.on('close', () => events.complete())
sftp.closed$.subscribe(() => watcher.close())
}, 1000)
const watcher = fs.watch(tempPath, event => events.next(event))
events.pipe(debounceTime(1000), debounce(async event => {
if (event === 'rename') {
watcher.close()
}
const upload = await this.platform.startUpload({ multiple: false }, [tempPath])
if (!upload.length) {
return
}
await sftp.upload(item.fullPath, upload[0])
await sftp.chmod(item.fullPath, item.mode)
})).subscribe()
watcher.on('close', () => events.complete())
sftp.closed$.subscribe(() => watcher.close())
}
}

View File

@@ -11,3 +11,8 @@ untildify@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b"
integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==
xterm-addon-web-links@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.7.0.tgz#dceac36170605f9db10a01d716bd83ee38f65c17"
integrity sha512-6PqoqzzPwaeSq22skzbvyboDvSnYk5teUYEoKBwMYvhbkwOQkemZccjWHT5FnNA8o1aInTc4PRYAl4jjPucCKA==

View File

@@ -40,11 +40,11 @@
div(translate) Report a problem
small.text-muted(translate) Generate a pre-filled GitHub issue
button.list-group-item.list-group-item-action.link-card((click)='homeBase.openDiscord()')
i.fab.fa-fw.fa-discord
button.list-group-item.list-group-item-action.link-card((click)='homeBase.openDiscussions()')
i.fas.fa-fw.fa-comments
div
div(translate) Community
small.text-muted(translate) On Discord
div(translate) Ask a question
small.text-muted(translate) On GitHub Discussions
button.list-group-item.list-group-item-action.link-card((click)='homeBase.openGitHub()')
i.fab.fa-fw.fa-github
@@ -58,6 +58,12 @@
div(translate) What's new
small.text-muted(translate) Show release notes
button.list-group-item.list-group-item-action.link-card((click)='homeBase.openTwitter()')
i.fab.fa-fw.fa-twitter
div
div(translate) Subscribe to updates
small.text-muted(translate) Tabby news and updates on Twitter
h3(translate) Application settings
.form-line

View File

@@ -123,7 +123,7 @@ export class VaultSettingsTabComponent extends BaseComponent {
}
await this.vault.updateSecret(secret, {
...secret,
value: Buffer.from(await transfers[0].readAll()).toString('base64'),
value: (await transfers[0].readAll()).toString('base64'),
})
this.loadVault()
}

View File

@@ -121,15 +121,6 @@ h3.mb-3(translate) Window
label.btn.btn-secondary(for='frameFull')
span(translate) Full
.form-line(*ngIf='hostApp.platform !== Platform.Web && hostApp.platform !== Platform.Linux')
.header
.title(translate) Hide tray
.description(translate) Hide Tabby in tray or menu bar.
toggle(
[(ngModel)]='config.store.hideTray',
(ngModelChange)='saveConfiguration(true)'
)
h3.mt-4(translate) Docking
.form-line(*ngIf='docking')

View File

@@ -11,17 +11,22 @@
"build": "webpack --progress --color",
"watch": "webpack --progress --color --watch",
"postinstall": "run-script-os",
"postinstall:darwin:linux": "exit"
"postinstall:darwin:linux": "exit",
"postinstall:win32": "xcopy /i /y ..\\node_modules\\ssh2\\util\\pagent.exe util\\"
},
"files": [
"dist",
"util/pagent.exe",
"typings"
],
"author": "Eugene Pankov",
"license": "MIT",
"devDependencies": {
"@types/node": "20.3.1",
"@types/ssh2": "^0.5.46",
"ansi-colors": "^4.1.1",
"diffie-hellman": "^5.0.3",
"sshpk": "Eugeny/node-sshpk#c2b71d1243714d2daf0988f84c3323d180817136",
"strip-ansi": "^7.0.0"
},
"dependencies": {
@@ -40,8 +45,5 @@
"tabby-core": "*",
"tabby-settings": "*",
"tabby-terminal": "*"
},
"resolutions": {
"glob": "7.2.3"
}
}

View File

@@ -1,45 +1,20 @@
import * as russh from 'russh'
import { SSHAlgorithmType } from './api'
import * as ALGORITHMS from 'ssh2/lib/protocol/constants'
import { ALGORITHM_BLACKLIST, SSHAlgorithmType } from './api'
export const supportedAlgorithms = {
[SSHAlgorithmType.KEX]: russh.getSupportedKexAlgorithms().filter(x => x !== 'none'),
[SSHAlgorithmType.HOSTKEY]: russh.getSupportedKeyTypes().filter(x => x !== 'none'),
[SSHAlgorithmType.CIPHER]: russh.getSupportedCiphers().filter(x => x !== 'clear'),
[SSHAlgorithmType.HMAC]: russh.getSupportedMACs().filter(x => x !== 'none'),
}
// Counteracts https://github.com/mscdex/ssh2/commit/f1b5ac3c81734c194740016eab79a699efae83d8
ALGORITHMS.DEFAULT_CIPHER.push('aes128-gcm')
ALGORITHMS.DEFAULT_CIPHER.push('aes256-gcm')
ALGORITHMS.SUPPORTED_CIPHER.push('aes128-gcm')
ALGORITHMS.SUPPORTED_CIPHER.push('aes256-gcm')
export const defaultAlgorithms = {
[SSHAlgorithmType.KEX]: [
'curve25519-sha256',
'curve25519-sha256@libssh.org',
'diffie-hellman-group16-sha512',
'diffie-hellman-group14-sha256',
'ext-info-c',
'ext-info-s',
'kex-strict-c-v00@openssh.com',
'kex-strict-s-v00@openssh.com',
],
[SSHAlgorithmType.HOSTKEY]: [
'ssh-ed25519',
'ecdsa-sha2-nistp256',
'ecdsa-sha2-nistp521',
'rsa-sha2-256',
'rsa-sha2-512',
'ssh-rsa',
],
[SSHAlgorithmType.CIPHER]: [
'chacha20-poly1305@openssh.com',
'aes256-gcm@openssh.com',
'aes256-ctr',
'aes192-ctr',
'aes128-ctr',
],
[SSHAlgorithmType.HMAC]: [
'hmac-sha2-512-etm@openssh.com',
'hmac-sha2-256-etm@openssh.com',
'hmac-sha2-512',
'hmac-sha2-256',
'hmac-sha1-etm@openssh.com',
'hmac-sha1',
],
export const supportedAlgorithms: Record<string, string> = {}
for (const k of Object.values(SSHAlgorithmType)) {
const supportedAlg = {
[SSHAlgorithmType.KEX]: 'SUPPORTED_KEX',
[SSHAlgorithmType.HOSTKEY]: 'SUPPORTED_SERVER_HOST_KEY',
[SSHAlgorithmType.CIPHER]: 'SUPPORTED_CIPHER',
[SSHAlgorithmType.HMAC]: 'SUPPORTED_MAC',
}[k]
supportedAlgorithms[k] = ALGORITHMS[supportedAlg].filter(x => !ALGORITHM_BLACKLIST.includes(x)).sort()
}

View File

@@ -1,4 +1,4 @@
export * from './contextMenu'
export * from './interfaces'
export * from './importer'
export { SSHMultiplexerService } from '../services/sshMultiplexer.service'
export * from './proxyStream'

View File

@@ -51,3 +51,13 @@ export interface ForwardedPortConfig {
targetPort: number
description: string
}
export let ALGORITHM_BLACKLIST = [
// cause native crashes in node crypto, use EC instead
'diffie-hellman-group-exchange-sha256',
'diffie-hellman-group-exchange-sha1',
]
if (!process.env.TABBY_ENABLE_SSH_ALG_BLACKLIST) {
ALGORITHM_BLACKLIST = []
}

View File

@@ -0,0 +1,61 @@
import { Observable, Subject } from 'rxjs'
import { Duplex } from 'stream'
export class SSHProxyStreamSocket extends Duplex {
constructor (private parent: SSHProxyStream) {
super({
allowHalfOpen: false,
})
}
_read (size: number): void {
this.parent.requestData(size)
}
_write (chunk: Buffer, _encoding: string, callback: (error?: Error | null) => void): void {
this.parent.consumeInput(chunk).then(() => callback(null), e => callback(e))
}
_destroy (error: Error|null, callback: (error: Error|null) => void): void {
this.parent.handleStopRequest(error).then(() => callback(null), e => callback(e))
}
}
export abstract class SSHProxyStream {
get message$ (): Observable<string> { return this.message }
get destroyed$ (): Observable<Error|null> { return this.destroyed }
get socket (): SSHProxyStreamSocket|null { return this._socket }
private message = new Subject<string>()
private destroyed = new Subject<Error|null>()
private _socket: SSHProxyStreamSocket|null = null
async start (): Promise<SSHProxyStreamSocket> {
if (!this._socket) {
this._socket = new SSHProxyStreamSocket(this)
}
return this._socket
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
abstract requestData (size: number): void
abstract consumeInput (data: Buffer): Promise<void>
protected emitMessage (message: string): void {
this.message.next(message)
}
protected emitOutput (data: Buffer): void {
this._socket?.push(data)
}
async handleStopRequest (error: Error|null): Promise<void> {
this.destroyed.next(error)
this.destroyed.complete()
this.message.complete()
}
stop (error?: Error): void {
this._socket?.destroy(error)
}
}

View File

@@ -14,19 +14,11 @@ input.form-control.mt-2(
)
.d-flex.mt-3
checkbox(
*ngIf='isPassword()',
[(ngModel)]='remember',
[text]='"Save password"|translate'
)
.ms-auto
button.btn.btn-secondary.me-3(
button.btn.btn-secondary(
*ngIf='step > 0',
(click)='previous()'
)
.ms-auto
button.btn.btn-primary(
(click)='next()'
)

View File

@@ -1,7 +1,6 @@
import { Component, Input, Output, EventEmitter, ViewChild, ElementRef, ChangeDetectionStrategy } from '@angular/core'
import { KeyboardInteractivePrompt } from '../session/ssh'
import { SSHProfile } from '../api'
import { PasswordStorageService } from '../services/passwordStorage.service'
@Component({
selector: 'keyboard-interactive-auth-panel',
@@ -10,17 +9,13 @@ import { PasswordStorageService } from '../services/passwordStorage.service'
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class KeyboardInteractiveAuthComponent {
@Input() profile: SSHProfile
@Input() prompt: KeyboardInteractivePrompt
@Input() step = 0
@Output() done = new EventEmitter()
@ViewChild('input') input: ElementRef
remember = false
constructor (private passwordStorage: PasswordStorageService) {}
isPassword (): boolean {
return this.prompt.isAPasswordPrompt(this.step)
return this.prompt.prompts[this.step].prompt.toLowerCase().includes('password') || !this.prompt.prompts[this.step].echo
}
previous (): void {
@@ -31,10 +26,6 @@ export class KeyboardInteractiveAuthComponent {
}
next (): void {
if (this.isPassword() && this.remember) {
this.passwordStorage.savePassword(this.profile, this.prompt.responses[this.step])
}
if (this.step === this.prompt.prompts.length - 1) {
this.prompt.respond()
this.done.emit()

View File

@@ -23,15 +23,11 @@
button.btn.btn-link.btn-sm.flex-shrink-0.d-flex((click)='upload()')
i.fas.fa-upload.me-1
div(translate) Upload files
button.btn.btn-link.btn-sm.flex-shrink-0.d-flex((click)='uploadFolder()')
i.fas.fa-upload.me-1
div(translate) Upload folder
div(translate) Upload
button.btn.btn-link.text-decoration-none((click)='close()') !{require('../../../tabby-core/src/icons/times.svg')}
.body(dropZone, (transfer)='uploadOneFolder($event)')
.body(dropZone, (transfer)='uploadOne($event)')
a.alert.alert-info.d-flex.align-items-center(
*ngIf='shouldShowCWDTip && !cwdDetectionAvailable',
(click)='platform.openExternal("https://tabby.sh/go/cwd-detection")'

View File

@@ -1,7 +1,7 @@
import * as C from 'constants'
import { posix as path } from 'path'
import { Component, Input, Output, EventEmitter, Inject, Optional } from '@angular/core'
import { FileUpload, DirectoryUpload, MenuItemOptions, NotificationsService, PlatformService } from 'tabby-core'
import { FileUpload, MenuItemOptions, NotificationsService, PlatformService } from 'tabby-core'
import { SFTPSession, SFTPFile } from '../session/sftp'
import { SSHSession } from '../session/ssh'
import { SFTPContextMenuItemProvider } from '../api'
@@ -180,30 +180,6 @@ export class SFTPPanelComponent {
await Promise.all(transfers.map(t => this.uploadOne(t)))
}
async uploadFolder (): Promise<void> {
const transfer = await this.platform.startUploadDirectory()
await this.uploadOneFolder(transfer)
}
async uploadOneFolder (transfer: DirectoryUpload, accumPath = ''): Promise<void> {
const savedPath = this.path
for(const t of transfer.getChildrens()) {
if (t instanceof DirectoryUpload) {
try {
await this.sftp.mkdir(path.posix.join(this.path, accumPath, t.getName()))
} catch {
// Intentionally ignoring errors from making duplicate dirs.
}
await this.uploadOneFolder(t, path.posix.join(accumPath, t.getName()))
} else {
await this.sftp.upload(path.posix.join(this.path, accumPath, t.getName()), t)
}
}
if (this.path === savedPath) {
await this.navigate(this.path)
}
}
async uploadOne (transfer: FileUpload): Promise<void> {
const savedPath = this.path
await this.sftp.upload(path.join(this.path, transfer.getName()), transfer)

View File

@@ -1,10 +1,7 @@
terminal-toolbar([tab]='this')
i.fas.fa-xs.fa-circle.text-success.me-2(*ngIf='session && session.open')
i.fas.fa-xs.fa-circle.text-danger.me-2(*ngIf='!session || !session.open')
strong.me-auto(
style='user-select: text; cursor: text;'
onclick='event.stopPropagation()'
) {{profile.options.user}}@{{profile.options.host}}:{{profile.options.port}}
strong.me-auto {{profile.options.user}}@{{profile.options.host}}:{{profile.options.port}}
.me-2(
ngbDropdown,
@@ -51,7 +48,6 @@ sftp-panel.bg-dark(
keyboard-interactive-auth-panel.bg-dark(
*ngIf='activeKIPrompt',
[prompt]='activeKIPrompt',
[profile]='profile',
(click)='$event.stopPropagation()',
(done)='activeKIPrompt = null; frontend?.focus()'
)

View File

@@ -1,4 +1,3 @@
import * as russh from 'russh'
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'
import colors from 'ansi-colors'
import { Component, Injector, HostListener } from '@angular/core'
@@ -95,21 +94,17 @@ export class SSHTabComponent extends ConnectableTerminalTabComponent<SSHProfile>
}
})
if (!(jumpSession.ssh instanceof russh.AuthenticatedSSHClient)) {
throw new Error('Jump session is not authenticated yet somehow')
}
try {
session.jumpChannel = await jumpSession.ssh.openTCPForwardChannel({
addressToConnectTo: profile.options.host,
portToConnectTo: profile.options.port ?? 22,
originatorAddress: '127.0.0.1',
originatorPort: 0,
})
} catch (err) {
jumpSession.emitServiceMessage(colors.bgRed.black(' X ') + ` Could not set up port forward on ${jumpConnection.name}`)
throw err
}
session.jumpStream = await new Promise((resolve, reject) => jumpSession.ssh.forwardOut(
'127.0.0.1', 0, profile.options.host, profile.options.port ?? 22,
(err, stream) => {
if (err) {
jumpSession.emitServiceMessage(colors.bgRed.black(' X ') + ` Could not set up port forward on ${jumpConnection.name}`)
reject(err)
return
}
resolve(stream)
},
))
}
}
@@ -130,7 +125,7 @@ export class SSHTabComponent extends ConnectableTerminalTabComponent<SSHProfile>
})
if (!session.open) {
this.write('\r\n' + colors.black.bgWhite(' SSH ') + ` Connecting to ${session.profile.name}\r\n`)
this.write('\r\n' + colors.black.bgWhite(' SSH ') + ` Connecting to ${session.profile.options.host}\r\n`)
this.startSpinner(this.translate.instant(_('Connecting')))

View File

@@ -1,3 +1,5 @@
import './polyfills'
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { FormsModule } from '@angular/forms'

View File

@@ -0,0 +1,4 @@
const nodeCrypto = require('crypto')
const browserDH = require('diffie-hellman/browser')
nodeCrypto.createDiffieHellmanGroup = browserDH.createDiffieHellmanGroup
nodeCrypto.createDiffieHellman = browserDH.createDiffieHellman

View File

@@ -1,11 +1,11 @@
import { Injectable, InjectFlags, Injector } from '@angular/core'
import { NewTabParameters, PartialProfile, TranslateService, QuickConnectProfileProvider } from 'tabby-core'
import * as ALGORITHMS from 'ssh2/lib/protocol/constants'
import { SSHProfileSettingsComponent } from './components/sshProfileSettings.component'
import { SSHTabComponent } from './components/sshTab.component'
import { PasswordStorageService } from './services/passwordStorage.service'
import { SSHAlgorithmType, SSHProfile } from './api'
import { ALGORITHM_BLACKLIST, SSHAlgorithmType, SSHProfile } from './api'
import { SSHProfileImporter } from './api/importer'
import { defaultAlgorithms } from './algorithms'
@Injectable({ providedIn: 'root' })
export class SSHProfilesService extends QuickConnectProfileProvider<SSHProfile> {
@@ -29,10 +29,10 @@ export class SSHProfilesService extends QuickConnectProfileProvider<SSHProfile>
agentForward: false,
warnOnClose: null,
algorithms: {
hmac: [] as string[],
kex: [] as string[],
cipher: [] as string[],
serverHostKey: [] as string[],
hmac: [],
kex: [],
cipher: [],
serverHostKey: [],
},
proxyCommand: null,
forwardedPorts: [],
@@ -54,7 +54,13 @@ export class SSHProfilesService extends QuickConnectProfileProvider<SSHProfile>
) {
super()
for (const k of Object.values(SSHAlgorithmType)) {
this.configDefaults.options.algorithms[k] = [...defaultAlgorithms[k]]
const defaultAlg = {
[SSHAlgorithmType.KEX]: 'DEFAULT_KEX',
[SSHAlgorithmType.HOSTKEY]: 'DEFAULT_SERVER_HOST_KEY',
[SSHAlgorithmType.CIPHER]: 'DEFAULT_CIPHER',
[SSHAlgorithmType.HMAC]: 'DEFAULT_MAC',
}[k]
this.configDefaults.options.algorithms[k] = ALGORITHMS[defaultAlg].filter(x => !ALGORITHM_BLACKLIST.includes(x))
this.configDefaults.options.algorithms[k].sort()
}
}

View File

@@ -1,9 +1,15 @@
// import * as fs from 'fs/promises'
import * as shellQuote from 'shell-quote'
import * as net from 'net'
import * as fs from 'fs/promises'
import * as tmp from 'tmp-promise'
import socksv5 from '@luminati-io/socksv5'
import { Duplex } from 'stream'
import { Injectable } from '@angular/core'
import { spawn } from 'child_process'
import { ChildProcess } from 'node:child_process'
import { ConfigService, HostAppService, Platform, PlatformService } from 'tabby-core'
import { SSHSession } from '../session/ssh'
import { SSHProfile } from '../api'
import { SSHProfile, SSHProxyStream, SSHProxyStreamSocket } from '../api'
import { PasswordStorageService } from './passwordStorage.service'
@Injectable({ providedIn: 'root' })
@@ -49,7 +55,7 @@ export class SSHService {
let tmpFile: tmp.FileResult|null = null
if (session.activePrivateKey) {
tmpFile = await tmp.file()
// await fs.writeFile(tmpFile.path, session.activePrivateKey)
await fs.writeFile(tmpFile.path, session.activePrivateKey)
const winSCPcom = path.slice(0, -3) + 'com'
await this.platform.exec(winSCPcom, ['/keygen', tmpFile.path, `/output=${tmpFile.path}`])
args.push(`/privatekey=${tmpFile.path}`)
@@ -58,3 +64,171 @@ export class SSHService {
tmpFile?.cleanup()
}
}
export class ProxyCommandStream extends SSHProxyStream {
private process: ChildProcess|null
constructor (private command: string) {
super()
}
async start (): Promise<SSHProxyStreamSocket> {
const argv = shellQuote.parse(this.command)
this.process = spawn(argv[0], argv.slice(1), {
windowsHide: true,
stdio: ['pipe', 'pipe', 'pipe'],
})
this.process.on('error', error => {
this.stop(new Error(`Proxy command has failed to start: ${error.message}`))
})
this.process.on('exit', code => {
this.stop(new Error(`Proxy command has exited with code ${code}`))
})
this.process.stdout?.on('data', data => {
this.emitOutput(data)
})
this.process.stdout?.on('error', (err) => {
this.stop(err)
})
this.process.stderr?.on('data', data => {
this.emitMessage(data.toString())
})
return super.start()
}
requestData (size: number): void {
this.process?.stdout?.read(size)
}
async consumeInput (data: Buffer): Promise<void> {
const process = this.process
if (process) {
await new Promise(resolve => process.stdin?.write(data, resolve))
}
}
async stop (error?: Error): Promise<void> {
this.process?.kill()
super.stop(error)
}
}
export class SocksProxyStream extends SSHProxyStream {
private client: Duplex|null
private header: Buffer|null
constructor (private profile: SSHProfile) {
super()
}
async start (): Promise<SSHProxyStreamSocket> {
this.client = await new Promise((resolve, reject) => {
const connector = socksv5.connect({
host: this.profile.options.host,
port: this.profile.options.port,
proxyHost: this.profile.options.socksProxyHost ?? '127.0.0.1',
proxyPort: this.profile.options.socksProxyPort ?? 5000,
auths: [socksv5.auth.None()],
strictLocalDNS: false,
}, s => {
resolve(s)
this.header = s.read()
if (this.header) {
this.emitOutput(this.header)
}
})
connector.on('error', (err) => {
reject(err)
this.stop(new Error(`SOCKS connection failed: ${err.message}`))
})
})
this.client?.on('data', data => {
if (!this.header || data !== this.header) {
// socksv5 doesn't reliably emit the first data event
this.emitOutput(data)
this.header = null
}
})
this.client?.on('close', error => {
this.stop(error)
})
return super.start()
}
requestData (size: number): void {
this.client?.read(size)
}
async consumeInput (data: Buffer): Promise<void> {
return new Promise((resolve, reject) => {
this.client?.write(data, undefined, err => err ? reject(err) : resolve())
})
}
async stop (error?: Error): Promise<void> {
this.client?.destroy()
super.stop(error)
}
}
export class HTTPProxyStream extends SSHProxyStream {
private client: Duplex|null
private connected = false
constructor (private profile: SSHProfile) {
super()
}
async start (): Promise<SSHProxyStreamSocket> {
this.client = await new Promise((resolve, reject) => {
const connector = net.createConnection({
host: this.profile.options.httpProxyHost!,
port: this.profile.options.httpProxyPort!,
}, () => resolve(connector))
connector.on('error', error => {
reject(error)
this.stop(new Error(`Proxy connection failed: ${error.message}`))
})
})
this.client?.write(Buffer.from(`CONNECT ${this.profile.options.host}:${this.profile.options.port} HTTP/1.1\r\n\r\n`))
this.client?.on('data', (data: Buffer) => {
if (this.connected) {
this.emitOutput(data)
} else {
if (data.slice(0, 5).equals(Buffer.from('HTTP/'))) {
const idx = data.indexOf('\n\n')
const headers = data.slice(0, idx).toString()
const code = parseInt(headers.split(' ')[1])
if (code >= 200 && code < 300) {
this.emitMessage('Connected')
this.emitOutput(data.slice(idx + 2))
this.connected = true
} else {
this.stop(new Error(`Connection failed, code ${code}`))
}
}
}
})
this.client?.on('close', error => {
this.stop(error)
})
return super.start()
}
requestData (size: number): void {
this.client?.read(size)
}
async consumeInput (data: Buffer): Promise<void> {
return new Promise((resolve, reject) => {
this.client?.write(data, undefined, err => err ? reject(err) : resolve())
})
}
async stop (error?: Error): Promise<void> {
this.client?.destroy()
super.stop(error)
}
}

View File

@@ -1,9 +1,12 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import * as C from 'constants'
import { Subject, Observable } from 'rxjs'
import { posix as posixPath } from 'path'
import { Injector } from '@angular/core'
import { FileDownload, FileUpload, Logger, LogService } from 'tabby-core'
import * as russh from 'russh'
import { Injector, NgZone } from '@angular/core'
import { FileDownload, FileUpload, Logger, LogService, wrapPromise } from 'tabby-core'
import { SFTPWrapper } from 'ssh2'
import { promisify } from 'util'
import type { FileEntry, Stats } from 'ssh2-streams'
export interface SFTPFile {
name: string
@@ -19,37 +22,63 @@ export class SFTPFileHandle {
position = 0
constructor (
private inner: russh.SFTPFile|null,
private sftp: SFTPWrapper,
private handle: Buffer,
private zone: NgZone,
) { }
async read (): Promise<Uint8Array> {
if (!this.inner) {
return Promise.resolve(new Uint8Array(0))
}
return this.inner.read(256 * 1024)
read (): Promise<Buffer> {
const buffer = Buffer.alloc(256 * 1024)
return wrapPromise(this.zone, new Promise((resolve, reject) => {
while (true) {
const wait = this.sftp.read(this.handle, buffer, 0, buffer.length, this.position, (err, read) => {
if (err) {
reject(err)
return
}
this.position += read
resolve(buffer.slice(0, read))
})
if (!wait) {
break
}
}
}))
}
async write (chunk: Uint8Array): Promise<void> {
if (!this.inner) {
throw new Error('File handle is closed')
}
await this.inner.writeAll(chunk)
write (chunk: Buffer): Promise<void> {
return wrapPromise(this.zone, new Promise<void>((resolve, reject) => {
while (true) {
const wait = this.sftp.write(this.handle, chunk, 0, chunk.length, this.position, err => {
if (err) {
reject(err)
return
}
this.position += chunk.length
resolve()
})
if (!wait) {
break
}
}
}))
}
async close (): Promise<void> {
await this.inner?.shutdown()
this.inner = null
close (): Promise<void> {
return wrapPromise(this.zone, promisify(this.sftp.close.bind(this.sftp))(this.handle))
}
}
export class SFTPSession {
get closed$ (): Observable<void> { return this.closed }
private closed = new Subject<void>()
private zone: NgZone
private logger: Logger
constructor (private sftp: russh.SFTP, injector: Injector) {
constructor (private sftp: SFTPWrapper, injector: Injector) {
this.zone = injector.get(NgZone)
this.logger = injector.get(LogService).create('sftp')
sftp.closed$.subscribe(() => {
sftp.on('close', () => {
this.closed.next()
this.closed.complete()
})
@@ -57,64 +86,67 @@ export class SFTPSession {
async readdir (p: string): Promise<SFTPFile[]> {
this.logger.debug('readdir', p)
const entries = await this.sftp.readDirectory(p)
const entries = await wrapPromise(this.zone, promisify<FileEntry[]>(f => this.sftp.readdir(p, f))())
return entries.map(entry => this._makeFile(
posixPath.join(p, entry.name), entry,
posixPath.join(p, entry.filename), entry,
))
}
readlink (p: string): Promise<string> {
this.logger.debug('readlink', p)
return this.sftp.readlink(p)
return wrapPromise(this.zone, promisify<string>(f => this.sftp.readlink(p, f))())
}
async stat (p: string): Promise<SFTPFile> {
this.logger.debug('stat', p)
const stats = await this.sftp.stat(p)
const stats = await wrapPromise(this.zone, promisify<Stats>(f => this.sftp.stat(p, f))())
return {
name: posixPath.basename(p),
fullPath: p,
isDirectory: stats.type === russh.SFTPFileType.Directory,
isSymlink: stats.type === russh.SFTPFileType.Symlink,
mode: stats.permissions ?? 0,
isDirectory: stats.isDirectory(),
isSymlink: stats.isSymbolicLink(),
mode: stats.mode,
size: stats.size,
modified: new Date((stats.mtime ?? 0) * 1000),
modified: new Date(stats.mtime * 1000),
}
}
async open (p: string, mode: number): Promise<SFTPFileHandle> {
this.logger.debug('open', p, mode)
const handle = await this.sftp.open(p, mode)
return new SFTPFileHandle(handle)
async open (p: string, mode: string): Promise<SFTPFileHandle> {
this.logger.debug('open', p)
const handle = await wrapPromise(this.zone, promisify<Buffer>(f => this.sftp.open(p, mode, f))())
return new SFTPFileHandle(this.sftp, handle, this.zone)
}
async rmdir (p: string): Promise<void> {
await this.sftp.removeDirectory(p)
this.logger.debug('rmdir', p)
await promisify((f: any) => this.sftp.rmdir(p, f))()
}
async mkdir (p: string): Promise<void> {
await this.sftp.createDirectory(p)
this.logger.debug('mkdir', p)
await promisify((f: any) => this.sftp.mkdir(p, f))()
}
async rename (oldPath: string, newPath: string): Promise<void> {
this.logger.debug('rename', oldPath, newPath)
await this.sftp.rename(oldPath, newPath)
await promisify((f: any) => this.sftp.rename(oldPath, newPath, f))()
}
async unlink (p: string): Promise<void> {
await this.sftp.removeFile(p)
this.logger.debug('unlink', p)
await promisify((f: any) => this.sftp.unlink(p, f))()
}
async chmod (p: string, mode: string|number): Promise<void> {
this.logger.debug('chmod', p, mode)
await this.sftp.chmod(p, mode)
await promisify((f: any) => this.sftp.chmod(p, mode, f))()
}
async upload (path: string, transfer: FileUpload): Promise<void> {
this.logger.info('Uploading into', path)
const tempPath = path + '.tabby-upload'
try {
const handle = await this.open(tempPath, russh.OPEN_WRITE | russh.OPEN_CREATE)
const handle = await this.open(tempPath, 'w')
while (true) {
const chunk = await transfer.read()
if (!chunk.length) {
@@ -122,13 +154,15 @@ export class SFTPSession {
}
await handle.write(chunk)
}
await handle.close()
await this.unlink(path).catch(() => null)
handle.close()
try {
await this.unlink(path)
} catch { }
await this.rename(tempPath, path)
transfer.close()
} catch (e) {
transfer.cancel()
this.unlink(tempPath).catch(() => null)
this.unlink(tempPath)
throw e
}
}
@@ -136,7 +170,7 @@ export class SFTPSession {
async download (path: string, transfer: FileDownload): Promise<void> {
this.logger.info('Downloading', path)
try {
const handle = await this.open(path, russh.OPEN_READ)
const handle = await this.open(path, 'r')
while (true) {
const chunk = await handle.read()
if (!chunk.length) {
@@ -152,15 +186,15 @@ export class SFTPSession {
}
}
private _makeFile (p: string, entry: russh.SFTPDirectoryEntry): SFTPFile {
private _makeFile (p: string, entry: FileEntry): SFTPFile {
return {
fullPath: p,
name: posixPath.basename(p),
isDirectory: entry.metadata.type === russh.SFTPFileType.Directory,
isSymlink: entry.metadata.type === russh.SFTPFileType.Symlink,
mode: entry.metadata.permissions ?? 0,
size: entry.metadata.size,
modified: new Date((entry.metadata.mtime ?? 0) * 1000),
isDirectory: (entry.attrs.mode & C.S_IFDIR) === C.S_IFDIR,
isSymlink: (entry.attrs.mode & C.S_IFLNK) === C.S_IFLNK,
mode: entry.attrs.mode,
size: entry.attrs.size,
modified: new Date(entry.attrs.mtime * 1000),
}
}
}

View File

@@ -1,15 +1,15 @@
import { Observable, Subject } from 'rxjs'
import stripAnsi from 'strip-ansi'
import { ClientChannel } from 'ssh2'
import { Injector } from '@angular/core'
import { LogService } from 'tabby-core'
import { BaseSession, UTF8SplitterMiddleware, InputProcessor } from 'tabby-terminal'
import { SSHSession } from './ssh'
import { SSHProfile } from '../api'
import * as russh from 'russh'
export class SSHShellSession extends BaseSession {
shell?: russh.Channel
shell?: ClientChannel
get serviceMessage$ (): Observable<string> { return this.serviceMessage }
private serviceMessage = new Subject<string>()
private ssh: SSHSession|null
@@ -53,11 +53,19 @@ export class SSHShellSession extends BaseSession {
this.loginScriptProcessor?.executeUnconditionalScripts()
this.shell.data$.subscribe(data => {
this.emitOutput(Buffer.from(data))
this.shell.on('greeting', greeting => {
this.emitServiceMessage(`Shell greeting: ${greeting}`)
})
this.shell.eof$.subscribe(() => {
this.shell.on('banner', banner => {
this.emitServiceMessage(`Shell banner: ${banner}`)
})
this.shell.on('data', data => {
this.emitOutput(data)
})
this.shell.on('end', () => {
this.logger.info('Shell session ended')
if (this.open) {
this.destroy()
@@ -71,22 +79,19 @@ export class SSHShellSession extends BaseSession {
}
resize (columns: number, rows: number): void {
this.shell?.resizePTY({
columns,
rows,
pixHeight: 0,
pixWidth: 0,
})
if (this.shell) {
this.shell.setWindow(rows, columns, rows, columns)
}
}
write (data: Buffer): void {
if (this.shell) {
this.shell.write(new Uint8Array(data))
this.shell.write(data)
}
}
kill (_signal?: string): void {
// this.shell?.signal(signal ?? 'TERM')
kill (signal?: string): void {
this.shell?.signal(signal ?? 'TERM')
}
async destroy (): Promise<void> {

View File

@@ -1,22 +1,24 @@
import * as fs from 'mz/fs'
import * as crypto from 'crypto'
import * as sshpk from 'sshpk'
import colors from 'ansi-colors'
import stripAnsi from 'strip-ansi'
import * as shellQuote from 'shell-quote'
import { Injector } from '@angular/core'
import { Injector, NgZone } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { ConfigService, FileProvidersService, NotificationsService, PromptModalComponent, LogService, Logger, TranslateService, Platform, HostAppService } from 'tabby-core'
import { ConfigService, FileProvidersService, HostAppService, NotificationsService, Platform, PlatformService, wrapPromise, PromptModalComponent, LogService, Logger, TranslateService } from 'tabby-core'
import { Socket } from 'net'
import { Client, ClientChannel, SFTPWrapper } from 'ssh2'
import { Subject, Observable } from 'rxjs'
import { HostKeyPromptModalComponent } from '../components/hostKeyPromptModal.component'
import { HTTPProxyStream, ProxyCommandStream, SocksProxyStream } from '../services/ssh.service'
import { PasswordStorageService } from '../services/passwordStorage.service'
import { SSHKnownHostsService } from '../services/sshKnownHosts.service'
import { promisify } from 'util'
import { SFTPSession } from './sftp'
import { SSHAlgorithmType, SSHProfile, AutoPrivateKeyLocator, PortForwardType } from '../api'
import { SSHAlgorithmType, PortForwardType, SSHProfile, SSHProxyStream, AutoPrivateKeyLocator } from '../api'
import { ForwardedPort } from './forwards'
import { X11Socket } from './x11'
import { supportedAlgorithms } from '../algorithms'
import * as russh from 'russh'
const WINDOWS_OPENSSH_AGENT_PIPE = '\\\\.\\pipe\\openssh-ssh-agent'
@@ -25,74 +27,48 @@ export interface Prompt {
echo?: boolean
}
type AuthMethod = {
type: 'none'|'prompt-password'|'hostbased'
} | {
type: 'keyboard-interactive',
savedPassword?: string
} | {
type: 'saved-password',
password: string
} | {
type: 'publickey'
name: string
contents: Buffer
} | {
type: 'agent',
kind: 'unix-socket',
path: string
} | {
type: 'agent',
kind: 'named-pipe',
path: string
} | {
type: 'agent',
kind: 'pageant',
interface AuthMethod {
type: 'none'|'publickey'|'agent'|'password'|'keyboard-interactive'|'hostbased'
name?: string
contents?: Buffer
}
interface Handshake {
kex: string
serverHostKey: string
}
export class KeyboardInteractivePrompt {
readonly responses: string[] = []
private _resolve: (value: string[]) => void
private _reject: (reason: any) => void
readonly promise = new Promise<string[]>((resolve, reject) => {
this._resolve = resolve
this._reject = reject
})
responses: string[] = []
constructor (
public name: string,
public instruction: string,
public prompts: Prompt[],
private callback: (_: string[]) => void,
) {
this.responses = new Array(this.prompts.length).fill('')
}
isAPasswordPrompt (index: number): boolean {
return this.prompts[index].prompt.toLowerCase().includes('password') && !this.prompts[index].echo
}
respond (): void {
this._resolve(this.responses)
}
reject (): void {
this._reject(new Error('Keyboard-interactive auth rejected'))
this.callback(this.responses)
}
}
export class SSHSession {
shell?: russh.Channel
ssh: russh.SSHClient|russh.AuthenticatedSSHClient
sftp?: russh.SFTP
shell?: ClientChannel
ssh: Client
sftp?: SFTPWrapper
forwardedPorts: ForwardedPort[] = []
jumpChannel: russh.Channel|null = null
jumpStream: any
proxyCommandStream: SSHProxyStream|null = null
savedPassword?: string
get serviceMessage$ (): Observable<string> { return this.serviceMessage }
get keyboardInteractivePrompt$ (): Observable<KeyboardInteractivePrompt> { return this.keyboardInteractivePrompt }
get willDestroy$ (): Observable<void> { return this.willDestroy }
activePrivateKey: russh.KeyPair|null = null
agentPath?: string
activePrivateKey: string|null = null
authUsername: string|null = null
open = false
@@ -103,11 +79,15 @@ export class SSHSession {
private serviceMessage = new Subject<string>()
private keyboardInteractivePrompt = new Subject<KeyboardInteractivePrompt>()
private willDestroy = new Subject<void>()
private keychainPasswordUsed = false
private hostKeyDigest = ''
private passwordStorage: PasswordStorageService
private ngbModal: NgbModal
private hostApp: HostAppService
private platform: PlatformService
private notifications: NotificationsService
private zone: NgZone
private fileProviders: FileProvidersService
private config: ConfigService
private translate: TranslateService
@@ -123,7 +103,9 @@ export class SSHSession {
this.passwordStorage = injector.get(PasswordStorageService)
this.ngbModal = injector.get(NgbModal)
this.hostApp = injector.get(HostAppService)
this.platform = injector.get(PlatformService)
this.notifications = injector.get(NotificationsService)
this.zone = injector.get(NgZone)
this.fileProviders = injector.get(FileProvidersService)
this.config = injector.get(ConfigService)
this.translate = injector.get(TranslateService)
@@ -138,6 +120,27 @@ export class SSHSession {
}
async init (): Promise<void> {
if (this.hostApp.platform === Platform.Windows) {
if (this.config.store.ssh.agentType === 'auto') {
if (await fs.exists(WINDOWS_OPENSSH_AGENT_PIPE)) {
this.agentPath = WINDOWS_OPENSSH_AGENT_PIPE
} else {
if (
await this.platform.isProcessRunning('pageant.exe') ||
await this.platform.isProcessRunning('gpg-agent.exe')
) {
this.agentPath = 'pageant'
}
}
} else if (this.config.store.ssh.agentType === 'pageant') {
this.agentPath = 'pageant'
} else {
this.agentPath = this.config.store.ssh.agentPath || WINDOWS_OPENSSH_AGENT_PIPE
}
} else {
this.agentPath = process.env.SSH_AUTH_SOCK!
}
this.remainingAuthMethods = [{ type: 'none' }]
if (!this.profile.options.auth || this.profile.options.auth === 'publicKey') {
if (this.profile.options.privateKeys?.length) {
@@ -164,192 +167,181 @@ export class SSHSession {
}
}
}
if (!this.profile.options.auth || this.profile.options.auth === 'agent') {
const spec = await this.getAgentConnectionSpec()
if (!spec) {
this.emitServiceMessage(colors.bgYellow.yellow.black(' ! ') + ` Agent auth selected, but no running Agent process is found`)
if (!this.agentPath) {
this.emitServiceMessage(colors.bgYellow.yellow.black(' ! ') + ` Agent auth selected, but no running agent is detected`)
} else {
this.remainingAuthMethods.push({
type: 'agent',
...spec,
})
this.remainingAuthMethods.push({ type: 'agent' })
}
}
if (!this.profile.options.auth || this.profile.options.auth === 'password') {
if (this.profile.options.password) {
this.remainingAuthMethods.push({ type: 'saved-password', password: this.profile.options.password })
}
const password = await this.passwordStorage.loadPassword(this.profile)
if (password) {
this.remainingAuthMethods.push({ type: 'saved-password', password })
}
this.remainingAuthMethods.push({ type: 'prompt-password' })
this.remainingAuthMethods.push({ type: 'password' })
}
if (!this.profile.options.auth || this.profile.options.auth === 'keyboardInteractive') {
const savedPassword = this.profile.options.password ?? await this.passwordStorage.loadPassword(this.profile)
if (savedPassword) {
this.remainingAuthMethods.push({ type: 'keyboard-interactive', savedPassword })
}
this.remainingAuthMethods.push({ type: 'keyboard-interactive' })
}
this.remainingAuthMethods.push({ type: 'hostbased' })
}
private async getAgentConnectionSpec (): Promise<russh.AgentConnectionSpec|null> {
if (this.hostApp.platform === Platform.Windows) {
if (this.config.store.ssh.agentType === 'auto') {
if (await fs.exists(WINDOWS_OPENSSH_AGENT_PIPE)) {
return {
kind: 'named-pipe',
path: WINDOWS_OPENSSH_AGENT_PIPE,
}
} else if (russh.isPageantRunning()) {
return {
kind: 'pageant',
}
} else {
this.emitServiceMessage(colors.bgYellow.yellow.black(' ! ') + ` Agent auth selected, but no running Agent process is found`)
}
} else if (this.config.store.ssh.agentType === 'pageant') {
return {
kind: 'pageant',
}
} else {
return {
kind: 'named-pipe',
path: this.config.store.ssh.agentPath || WINDOWS_OPENSSH_AGENT_PIPE,
}
}
} else {
return {
kind: 'unix-socket',
path: process.env.SSH_AUTH_SOCK!,
}
}
return null
}
async openSFTP (): Promise<SFTPSession> {
if (!(this.ssh instanceof russh.AuthenticatedSSHClient)) {
throw new Error('Cannot open SFTP session before auth')
}
if (!this.sftp) {
this.sftp = await this.ssh.openSFTPChannel()
this.sftp = await wrapPromise(this.zone, promisify<SFTPWrapper>(f => this.ssh.sftp(f))())
}
return new SFTPSession(this.sftp, this.injector)
}
async start (): Promise<void> {
const log = (s: any) => this.emitServiceMessage(s)
const ssh = new Client()
this.ssh = ssh
await this.init()
let connected = false
const algorithms = {}
for (const key of Object.values(SSHAlgorithmType)) {
algorithms[key] = this.profile.options.algorithms![key].filter(x => supportedAlgorithms[key].includes(x))
}
// eslint-disable-next-line @typescript-eslint/init-declarations
let transport: russh.SshTransport
if (this.profile.options.proxyCommand) {
this.emitServiceMessage(colors.bgBlue.black(' Proxy command ') + ` Using ${this.profile.options.proxyCommand}`)
const argv = shellQuote.parse(this.profile.options.proxyCommand)
transport = await russh.SshTransport.newCommand(argv[0], argv.slice(1))
} else if (this.jumpChannel) {
transport = await russh.SshTransport.newSshChannel(await this.jumpChannel.take())
this.jumpChannel = null
} else if (this.profile.options.socksProxyHost) {
this.emitServiceMessage(colors.bgBlue.black(' Proxy ') + ` Using ${this.profile.options.socksProxyHost}:${this.profile.options.socksProxyPort}`)
transport = await russh.SshTransport.newSocksProxy(
this.profile.options.socksProxyHost,
this.profile.options.socksProxyPort ?? 1080,
this.profile.options.host,
this.profile.options.port ?? 22,
)
} else if (this.profile.options.httpProxyHost) {
this.emitServiceMessage(colors.bgBlue.black(' Proxy ') + ` Using ${this.profile.options.httpProxyHost}:${this.profile.options.httpProxyPort}`)
transport = await russh.SshTransport.newHttpProxy(
this.profile.options.httpProxyHost,
this.profile.options.httpProxyPort ?? 8080,
this.profile.options.host,
this.profile.options.port ?? 22,
)
} else {
transport = await russh.SshTransport.newSocket(`${this.profile.options.host.trim()}:${this.profile.options.port ?? 22}`)
}
this.ssh = await russh.SSHClient.connect(
transport,
async key => {
if (!await this.verifyHostKey(key)) {
return false
const hostVerifiedPromise: Promise<void> = new Promise((resolve, reject) => {
ssh.on('handshake', async handshake => {
if (!await this.verifyHostKey(handshake)) {
this.ssh.end()
reject(new Error('Host key verification failed'))
}
this.logger.info('Host key verified')
return true
},
{
preferred: {
ciphers: this.profile.options.algorithms?.[SSHAlgorithmType.CIPHER]?.filter(x => supportedAlgorithms[SSHAlgorithmType.CIPHER].includes(x)),
kex: this.profile.options.algorithms?.[SSHAlgorithmType.KEX]?.filter(x => supportedAlgorithms[SSHAlgorithmType.KEX].includes(x)),
mac: this.profile.options.algorithms?.[SSHAlgorithmType.HMAC]?.filter(x => supportedAlgorithms[SSHAlgorithmType.HMAC].includes(x)),
key: this.profile.options.algorithms?.[SSHAlgorithmType.HOSTKEY]?.filter(x => supportedAlgorithms[SSHAlgorithmType.HOSTKEY].includes(x)),
},
keepaliveIntervalSeconds: Math.round((this.profile.options.keepaliveInterval ?? 15000) / 1000),
this.logger.info('Handshake complete:', handshake)
resolve()
})
})
const resultPromise: Promise<void> = new Promise(async (resolve, reject) => {
ssh.on('ready', () => {
connected = true
if (this.savedPassword) {
this.passwordStorage.savePassword(this.profile, this.savedPassword)
}
this.zone.run(resolve)
})
ssh.on('error', error => {
if (error.message === 'All configured authentication methods failed') {
this.passwordStorage.deletePassword(this.profile)
}
this.zone.run(() => {
if (connected) {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
this.notifications.error(error.toString())
} else {
reject(error)
}
})
})
ssh.on('close', () => {
if (this.open) {
this.destroy()
}
})
ssh.on('keyboard-interactive', (name, instructions, instructionsLang, prompts, finish) => this.zone.run(async () => {
this.emitKeyboardInteractivePrompt(new KeyboardInteractivePrompt(
name,
instructions,
prompts,
finish,
))
}))
ssh.on('greeting', greeting => {
if (!this.profile.options.skipBanner) {
log('Greeting: ' + greeting)
}
})
ssh.on('banner', banner => {
if (!this.profile.options.skipBanner) {
log(banner)
}
})
})
try {
if (this.profile.options.socksProxyHost) {
this.emitServiceMessage(colors.bgBlue.black(' Proxy ') + ` Using ${this.profile.options.socksProxyHost}:${this.profile.options.socksProxyPort}`)
this.proxyCommandStream = new SocksProxyStream(this.profile)
}
if (this.profile.options.httpProxyHost) {
this.emitServiceMessage(colors.bgBlue.black(' Proxy ') + ` Using ${this.profile.options.httpProxyHost}:${this.profile.options.httpProxyPort}`)
this.proxyCommandStream = new HTTPProxyStream(this.profile)
}
if (this.profile.options.proxyCommand) {
this.emitServiceMessage(colors.bgBlue.black(' Proxy command ') + ` Using ${this.profile.options.proxyCommand}`)
this.proxyCommandStream = new ProxyCommandStream(this.profile.options.proxyCommand)
}
if (this.proxyCommandStream) {
this.proxyCommandStream.destroyed$.subscribe(err => {
if (err) {
this.emitServiceMessage(colors.bgRed.black(' X ') + ` ${err.message}`)
this.destroy()
}
})
this.proxyCommandStream.message$.subscribe(message => {
this.emitServiceMessage(colors.bgBlue.black(' Proxy ') + ' ' + message.trim())
})
await this.proxyCommandStream.start()
}
this.authUsername ??= this.profile.options.user
if (!this.authUsername) {
const modal = this.ngbModal.open(PromptModalComponent)
modal.componentInstance.prompt = `Username for ${this.profile.options.host}`
try {
const result = await modal.result.catch(() => null)
this.authUsername = result?.value ?? null
} catch {
this.authUsername = 'root'
}
}
if (this.authUsername?.startsWith('$')) {
try {
const result = process.env[this.authUsername.slice(1)]
this.authUsername = result ?? this.authUsername
} catch {
this.authUsername = 'root'
}
}
ssh.connect({
host: this.profile.options.host.trim(),
port: this.profile.options.port ?? 22,
sock: this.proxyCommandStream?.socket ?? this.jumpStream,
username: this.authUsername ?? undefined,
tryKeyboard: true,
agent: this.agentPath,
agentForward: this.profile.options.agentForward && !!this.agentPath,
keepaliveInterval: this.profile.options.keepaliveInterval ?? 15000,
keepaliveCountMax: this.profile.options.keepaliveCountMax,
connectionTimeoutSeconds: this.profile.options.readyTimeout ? Math.round(this.profile.options.readyTimeout / 1000) : undefined,
},
)
this.ssh.banner$.subscribe(banner => {
if (!this.profile.options.skipBanner) {
this.emitServiceMessage(banner)
}
})
this.ssh.disconnect$.subscribe(() => {
if (this.open) {
this.destroy()
}
})
// Authentication
this.authUsername ??= this.profile.options.user
if (!this.authUsername) {
const modal = this.ngbModal.open(PromptModalComponent)
modal.componentInstance.prompt = `Username for ${this.profile.options.host}`
try {
const result = await modal.result.catch(() => null)
this.authUsername = result?.value ?? null
} catch {
this.authUsername = 'root'
}
readyTimeout: this.profile.options.readyTimeout,
hostVerifier: (key: any) => {
this.hostKeyDigest = crypto.createHash('sha256').update(key).digest('base64')
return true
},
algorithms,
authHandler: (methodsLeft, partialSuccess, callback) => {
this.zone.run(async () => {
callback(await this.handleAuth(methodsLeft))
})
},
})
} catch (e) {
this.notifications.error(e.message)
throw e
}
if (this.authUsername?.startsWith('$')) {
try {
const result = process.env[this.authUsername.slice(1)]
this.authUsername = result ?? this.authUsername
} catch {
this.authUsername = 'root'
}
}
const authenticatedClient = await this.handleAuth()
if (authenticatedClient) {
this.ssh = authenticatedClient
} else {
this.ssh.disconnect()
this.passwordStorage.deletePassword(this.profile)
// eslint-disable-next-line @typescript-eslint/no-base-to-string
throw new Error('Authentication rejected')
}
// auth success
if (this.savedPassword) {
this.passwordStorage.savePassword(this.profile, this.savedPassword)
}
await resultPromise
await hostVerifiedPromise
for (const fw of this.profile.options.forwardedPorts ?? []) {
this.addPortForward(Object.assign(new ForwardedPort(), fw))
@@ -357,11 +349,12 @@ export class SSHSession {
this.open = true
this.ssh.tcpChannelOpen$.subscribe(async event => {
this.logger.info(`Incoming forwarded connection: ${event.clientAddress}:${event.clientPort} -> ${event.targetAddress}:${event.targetPort}`)
const forward = this.forwardedPorts.find(x => x.port === event.targetPort && x.host === event.targetAddress)
this.ssh.on('tcp connection', (details, accept, reject) => {
this.logger.info(`Incoming forwarded connection: (remote) ${details.srcIP}:${details.srcPort} -> (local) ${details.destIP}:${details.destPort}`)
const forward = this.forwardedPorts.find(x => x.port === details.destPort)
if (!forward) {
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Rejected incoming forwarded connection for unrecognized port ${event.targetAddress}:${event.targetPort}`)
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Rejected incoming forwarded connection for unrecognized port ${details.destPort}`)
reject()
return
}
const socket = new Socket()
@@ -369,19 +362,24 @@ export class SSHSession {
socket.on('error', e => {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Could not forward the remote connection to ${forward.targetAddress}:${forward.targetPort}: ${e}`)
event.channel.close()
reject()
})
event.channel.data$.subscribe(data => socket.write(data))
socket.on('data', data => event.channel.write(Uint8Array.from(data)))
event.channel.closed$.subscribe(() => socket.destroy())
socket.on('close', () => event.channel.close())
socket.on('connect', () => {
this.logger.info('Connection forwarded')
const stream = accept()
stream.pipe(socket)
socket.pipe(stream)
stream.on('close', () => {
socket.destroy()
})
socket.on('close', () => {
stream.close()
})
})
})
this.ssh.x11ChannelOpen$.subscribe(async event => {
this.logger.info(`Incoming X11 connection from ${event.clientAddress}:${event.clientPort}`)
this.ssh.on('x11', async (details, accept, reject) => {
this.logger.info(`Incoming X11 connection from ${details.srcIP}:${details.srcPort}`)
const displaySpec = (this.config.store.ssh.x11Display || process.env.DISPLAY) ?? 'localhost:0'
this.logger.debug(`Trying display ${displaySpec}`)
@@ -389,18 +387,14 @@ export class SSHSession {
try {
const x11Stream = await socket.connect(displaySpec)
this.logger.info('Connection forwarded')
event.channel.data$.subscribe(data => {
x11Stream.write(data)
})
x11Stream.on('data', data => {
event.channel.write(Uint8Array.from(data))
})
event.channel.closed$.subscribe(() => {
const stream = accept()
stream.pipe(x11Stream)
x11Stream.pipe(stream)
stream.on('close', () => {
socket.destroy()
})
x11Stream.on('close', () => {
event.channel.close()
stream.close()
})
} catch (e) {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
@@ -411,43 +405,27 @@ export class SSHSession {
this.emitServiceMessage(' * VcXsrv: https://sourceforge.net/projects/vcxsrv/')
this.emitServiceMessage(' * Xming: https://sourceforge.net/projects/xming/')
}
event.channel.close()
reject()
}
})
this.ssh.agentChannelOpen$.subscribe(async channel => {
const spec = await this.getAgentConnectionSpec()
if (!spec) {
await channel.close()
return
}
const agent = await russh.SSHAgentStream.connect(spec)
channel.data$.subscribe(data => agent.write(data))
agent.data$.subscribe(data => channel.write(data), undefined, () => channel.close())
channel.closed$.subscribe(() => agent.close())
})
}
private async verifyHostKey (key: russh.SshPublicKey): Promise<boolean> {
private async verifyHostKey (handshake: Handshake): Promise<boolean> {
this.emitServiceMessage('Host key fingerprint:')
this.emitServiceMessage(colors.white.bgBlack(` ${key.algorithm()} `) + colors.bgBlackBright(' ' + key.fingerprint() + ' '))
this.emitServiceMessage(colors.white.bgBlack(` ${handshake.serverHostKey} `) + colors.bgBlackBright(' ' + this.hostKeyDigest + ' '))
if (!this.config.store.ssh.verifyHostKeys) {
return true
}
const selector = {
host: this.profile.options.host,
port: this.profile.options.port ?? 22,
type: key.algorithm(),
type: handshake.serverHostKey,
}
const keyDigest = crypto.createHash('sha256').update(key.bytes()).digest('base64')
const knownHost = this.knownHosts.getFor(selector)
if (!knownHost || knownHost.digest !== keyDigest) {
if (!knownHost || knownHost.digest !== this.hostKeyDigest) {
const modal = this.ngbModal.open(HostKeyPromptModalComponent)
modal.componentInstance.selector = selector
modal.componentInstance.digest = keyDigest
modal.componentInstance.digest = this.hostKeyDigest
return modal.result.catch(() => false)
}
return true
@@ -469,49 +447,57 @@ export class SSHSession {
this.keyboardInteractivePrompt.next(prompt)
}
async handleAuth (methodsLeft?: string[] | null): Promise<russh.AuthenticatedSSHClient|null> {
async handleAuth (methodsLeft?: string[] | null): Promise<any> {
this.activePrivateKey = null
if (!(this.ssh instanceof russh.SSHClient)) {
throw new Error('Wrong state for auth handling')
}
if (!this.authUsername) {
throw new Error('No username')
}
while (true) {
const method = this.remainingAuthMethods.shift()
if (!method) {
return null
return false
}
if (methodsLeft && !methodsLeft.includes(method.type) && method.type !== 'agent') {
// Agent can still be used even if not in methodsLeft
this.logger.info('Server does not support auth method', method.type)
continue
}
if (method.type === 'saved-password') {
this.emitServiceMessage(this.translate.instant('Using saved password'))
const result = await this.ssh.authenticateWithPassword(this.authUsername, method.password)
if (result) {
return result
if (method.type === 'password') {
if (this.profile.options.password) {
this.emitServiceMessage(this.translate.instant('Using preset password'))
return {
type: 'password',
username: this.authUsername,
password: this.profile.options.password,
}
}
}
if (method.type === 'prompt-password') {
if (!this.keychainPasswordUsed && this.profile.options.user) {
const password = await this.passwordStorage.loadPassword(this.profile)
if (password) {
this.emitServiceMessage(this.translate.instant('Trying saved password'))
this.keychainPasswordUsed = true
return {
type: 'password',
username: this.authUsername,
password,
}
}
}
const modal = this.ngbModal.open(PromptModalComponent)
modal.componentInstance.prompt = `Password for ${this.authUsername}@${this.profile.options.host}`
modal.componentInstance.password = true
modal.componentInstance.showRememberCheckbox = true
try {
const promptResult = await modal.result.catch(() => null)
if (promptResult) {
if (promptResult.remember) {
this.savedPassword = promptResult.value
const result = await modal.result.catch(() => null)
if (result) {
if (result.remember) {
this.savedPassword = result.value
}
const result = await this.ssh.authenticateWithPassword(this.authUsername, promptResult.value)
if (result) {
return result
return {
type: 'password',
username: this.authUsername,
password: result.value,
}
} else {
continue
@@ -520,104 +506,50 @@ export class SSHSession {
continue
}
}
if (method.type === 'publickey') {
if (method.type === 'publickey' && method.contents) {
try {
const key = await this.loadPrivateKey(method.name, method.contents)
const result = await this.ssh.authenticateWithKeyPair(this.authUsername, key)
if (result) {
return result
const key = await this.loadPrivateKey(method.name!, method.contents)
return {
type: 'publickey',
username: this.authUsername,
key,
}
} catch (e) {
this.emitServiceMessage(colors.bgYellow.yellow.black(' ! ') + ` Failed to load private key ${method.name}: ${e}`)
continue
}
}
if (method.type === 'keyboard-interactive') {
let state: russh.AuthenticatedSSHClient|russh.KeyboardInteractiveAuthenticationState = await this.ssh.startKeyboardInteractiveAuthentication(this.authUsername)
while (true) {
if (state.state === 'failure') {
break
}
const prompts = state.prompts()
let responses: string[] = []
// OpenSSH can send a k-i request without prompts
// just respond ok to it
if (prompts.length > 0) {
const prompt = new KeyboardInteractivePrompt(
state.name,
state.instructions,
state.prompts(),
)
if (method.savedPassword) {
// eslint-disable-next-line max-depth
for (let i = 0; i < prompt.prompts.length; i++) {
// eslint-disable-next-line max-depth
if (prompt.isAPasswordPrompt(i)) {
prompt.responses[i] = method.savedPassword
}
}
}
this.emitKeyboardInteractivePrompt(prompt)
try {
// eslint-disable-next-line @typescript-eslint/await-thenable
responses = await prompt.promise
} catch {
break // this loop
}
}
state = await this.ssh .continueKeyboardInteractiveAuthentication(responses)
if (state instanceof russh.AuthenticatedSSHClient) {
return state
}
}
}
if (method.type === 'agent') {
try {
const result = await this.ssh.authenticateWithAgent(this.authUsername, method)
if (result) {
return result
}
} catch (e) {
this.emitServiceMessage(colors.bgYellow.yellow.black(' ! ') + ` Failed to authenticate using agent: ${e}`)
continue
}
}
return method.type
}
return null
}
async addPortForward (fw: ForwardedPort): Promise<void> {
if (fw.type === PortForwardType.Local || fw.type === PortForwardType.Dynamic) {
await fw.startLocalListener(async (accept, reject, sourceAddress, sourcePort, targetAddress, targetPort) => {
await fw.startLocalListener((accept, reject, sourceAddress, sourcePort, targetAddress, targetPort) => {
this.logger.info(`New connection on ${fw}`)
if (!(this.ssh instanceof russh.AuthenticatedSSHClient)) {
this.logger.error(`Connection while unauthenticated on ${fw}`)
reject()
return
}
const channel = await this.ssh.openTCPForwardChannel({
addressToConnectTo: targetAddress,
portToConnectTo: targetPort,
originatorAddress: sourceAddress ?? '127.0.0.1',
originatorPort: sourcePort ?? 0,
}).catch(err => {
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Remote has rejected the forwarded connection to ${targetAddress}:${targetPort} via ${fw}: ${err}`)
reject()
throw err
})
const socket = accept()
channel.data$.subscribe(data => socket.write(data))
socket.on('data', data => channel.write(Uint8Array.from(data)))
channel.closed$.subscribe(() => socket.destroy())
socket.on('close', () => channel.close())
this.ssh.forwardOut(
sourceAddress ?? '127.0.0.1',
sourcePort ?? 0,
targetAddress,
targetPort,
(err, stream) => {
if (err) {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Remote has rejected the forwarded connection to ${targetAddress}:${targetPort} via ${fw}: ${err}`)
reject()
return
}
const socket = accept()
stream.pipe(socket)
socket.pipe(stream)
stream.on('close', () => {
socket.destroy()
})
socket.on('close', () => {
stream.close()
})
},
)
}).then(() => {
this.emitServiceMessage(colors.bgGreen.black(' -> ') + ` Forwarded ${fw}`)
this.forwardedPorts.push(fw)
@@ -627,16 +559,17 @@ export class SSHSession {
})
}
if (fw.type === PortForwardType.Remote) {
if (!(this.ssh instanceof russh.AuthenticatedSSHClient)) {
throw new Error('Cannot add remote port forward before auth')
}
try {
await this.ssh.forwardTCPPort(fw.host, fw.port)
} catch (err) {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Remote rejected port forwarding for ${fw}: ${err}`)
return
}
await new Promise<void>((resolve, reject) => {
this.ssh.forwardIn(fw.host, fw.port, err => {
if (err) {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Remote rejected port forwarding for ${fw}: ${err}`)
reject(err)
return
}
resolve()
})
})
this.emitServiceMessage(colors.bgGreen.black(' <- ') + ` Forwarded ${fw}`)
this.forwardedPorts.push(fw)
}
@@ -648,10 +581,7 @@ export class SSHSession {
this.forwardedPorts = this.forwardedPorts.filter(x => x !== fw)
}
if (fw.type === PortForwardType.Remote) {
if (!(this.ssh instanceof russh.AuthenticatedSSHClient)) {
throw new Error('Cannot remove remote port forward before auth')
}
this.ssh.stopForwardingTCPPort(fw.host, fw.port)
this.ssh.unforwardIn(fw.host, fw.port)
this.forwardedPorts = this.forwardedPorts.filter(x => x !== fw)
}
this.emitServiceMessage(`Stopped forwarding ${fw}`)
@@ -662,55 +592,43 @@ export class SSHSession {
this.willDestroy.next()
this.willDestroy.complete()
this.serviceMessage.complete()
this.ssh.disconnect()
this.proxyCommandStream?.stop()
this.ssh.end()
}
async openShellChannel (options: { x11: boolean }): Promise<russh.Channel> {
if (!(this.ssh instanceof russh.AuthenticatedSSHClient)) {
throw new Error('Cannot open shell channel before auth')
}
const ch = await this.ssh.openSessionChannel()
await ch.requestPTY('xterm-256color', {
columns: 80,
rows: 24,
pixHeight: 0,
pixWidth: 0,
})
if (options.x11) {
await ch.requestX11Forwarding({
singleConnection: false,
authProtocol: 'MIT-MAGIC-COOKIE-1',
authCookie: crypto.randomBytes(16).toString('hex'),
screenNumber: 0,
openShellChannel (options: { x11: boolean }): Promise<ClientChannel> {
return new Promise<ClientChannel>((resolve, reject) => {
this.ssh.shell({ term: 'xterm-256color' }, options, (err, shell) => {
if (err) {
reject(err)
} else {
resolve(shell)
}
})
}
if (this.profile.options.agentForward) {
await ch.requestAgentForwarding()
}
await ch.requestShell()
return ch
})
}
async loadPrivateKey (name: string, privateKeyContents: Buffer): Promise<russh.KeyPair> {
async loadPrivateKey (name: string, privateKeyContents: Buffer): Promise<string|null> {
this.emitServiceMessage(`Loading private key: ${name}`)
this.activePrivateKey = await this.loadPrivateKeyWithPassphraseMaybe(privateKeyContents.toString())
const parsedKey = await this.parsePrivateKey(privateKeyContents.toString())
this.activePrivateKey = parsedKey.toString('openssh')
return this.activePrivateKey
}
async loadPrivateKeyWithPassphraseMaybe (privateKey: string): Promise<russh.KeyPair> {
async parsePrivateKey (privateKey: string): Promise<any> {
const keyHash = crypto.createHash('sha512').update(privateKey).digest('hex')
let triedSavedPassphrase = false
let passphrase: string|null = null
while (true) {
try {
return await russh.KeyPair.parse(privateKey, passphrase ?? undefined)
return sshpk.parsePrivateKey(privateKey, 'auto', { passphrase })
} catch (e) {
if (!triedSavedPassphrase) {
passphrase = await this.passwordStorage.loadPrivateKeyPassword(keyHash)
triedSavedPassphrase = true
continue
}
if (e.toString() === 'Error: Keys(KeyIsEncrypted)' || e.toString() === 'Error: Keys(SshKey(Crypto))') {
if (e instanceof sshpk.KeyEncryptedError || e instanceof sshpk.KeyParseError) {
await this.passwordStorage.deletePrivateKeyPassword(keyHash)
const modal = this.ngbModal.open(PromptModalComponent)

View File

@@ -7,4 +7,9 @@ import config from '../webpack.plugin.config.mjs'
export default () => config({
name: 'ssh',
dirname: __dirname,
alias: {
'cpu-features': false,
'./crypto/build/Release/sshcrypto.node': false,
'../build/Release/cpufeatures.node': false,
},
})

View File

@@ -9,11 +9,26 @@
dependencies:
ipv6 "*"
"@types/node@20.3.1":
"@types/node@*", "@types/node@20.3.1":
version "20.3.1"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.1.tgz#e8a83f1aa8b649377bb1fb5d7bac5cb90e784dfe"
integrity sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==
"@types/ssh2-streams@*":
version "0.1.8"
resolved "https://registry.yarnpkg.com/@types/ssh2-streams/-/ssh2-streams-0.1.8.tgz#142af404dae059931aea7fcd1511b5478964feb6"
integrity sha512-I7gixRPUvVIyJuCEvnmhr3KvA2dC0639kKswqD4H5b4/FOcnPtNU+qWLiXdKIqqX9twUvi5j0U1mwKE5CUsrfA==
dependencies:
"@types/node" "*"
"@types/ssh2@^0.5.46":
version "0.5.52"
resolved "https://registry.yarnpkg.com/@types/ssh2/-/ssh2-0.5.52.tgz#9dbd8084e2a976e551d5e5e70b978ed8b5965741"
integrity sha512-lbLLlXxdCZOSJMCInKH2+9V/77ET2J6NPQHpFI0kda61Dd1KglJs+fPQBchizmzYSOJBgdTajhPqBO1xxLywvg==
dependencies:
"@types/node" "*"
"@types/ssh2-streams" "*"
ansi-colors@^4.1.1:
version "4.1.3"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b"
@@ -24,16 +39,40 @@ ansi-regex@^6.0.1:
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a"
integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==
asn1@~0.2.3:
version "0.2.4"
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==
dependencies:
safer-buffer "~2.1.0"
assert-plus@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
async@0.2.x:
version "0.2.10"
resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
integrity sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ==
integrity sha1-trvgsGdLnXGXCMo43owjfLUmw9E=
balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
bcrypt-pbkdf@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=
dependencies:
tweetnacl "^0.14.3"
bn.js@^4.0.0, bn.js@^4.1.0:
version "4.12.0"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88"
integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@@ -42,17 +81,22 @@ brace-expansion@^1.1.7:
balanced-match "^1.0.0"
concat-map "0.0.1"
brorand@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==
cli@0.4.x:
version "0.4.5"
resolved "https://registry.yarnpkg.com/cli/-/cli-0.4.5.tgz#78f9485cd161b566e9a6c72d7170c4270e81db61"
integrity sha512-dbn5HyeJWSOU58RwOEiF1VWrl7HRvDsKLpu0uiI/vExH6iNoyUzjB5Mr3IJY5DVUfnbpe9793xw4DFJVzC9nWQ==
integrity sha1-ePlIXNFhtWbppsctcXDEJw6B22E=
dependencies:
glob ">= 3.1.4"
cliff@0.1.x:
version "0.1.10"
resolved "https://registry.yarnpkg.com/cliff/-/cliff-0.1.10.tgz#53be33ea9f59bec85609ee300ac4207603e52013"
integrity sha512-roZWcC2Cxo/kKjRXw7YUpVNtxJccbvcl7VzTjUYgLQk6Ot0R8bm2netbhSZYWWNrKlOO/7HD6GXHl8dtzE6SiQ==
integrity sha1-U74z6p9ZvshWCe4wCsQgdgPlIBM=
dependencies:
colors "~1.0.3"
eyes "~0.1.8"
@@ -61,12 +105,12 @@ cliff@0.1.x:
colors@0.6.x:
version "0.6.2"
resolved "https://registry.yarnpkg.com/colors/-/colors-0.6.2.tgz#2423fe6678ac0c5dae8852e5d0e5be08c997abcc"
integrity sha512-OsSVtHK8Ir8r3+Fxw/b4jS1ZLPXkV6ZxDRJQzeD7qo0SqMXWrHDM71DgYzPMHY8SFJ0Ao+nNU2p1MmwdzKqPrw==
integrity sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=
colors@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
integrity sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==
integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=
concat-map@0.0.1:
version "0.0.1"
@@ -76,19 +120,62 @@ concat-map@0.0.1:
cycle@1.0.x:
version "1.0.3"
resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2"
integrity sha512-TVF6svNzeQCOpjCqsy0/CSy8VgObG3wXusJ73xW2GbG5rGx7lC8zxDSURicsXI2UsGdi2L0QNRCi745/wUDvsA==
integrity sha1-IegLK+hYD5i0aPN5QwZisEbDStI=
dashdash@^1.12.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=
dependencies:
assert-plus "^1.0.0"
diffie-hellman@^5.0.3:
version "5.0.3"
resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"
integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==
dependencies:
bn.js "^4.1.0"
miller-rabin "^4.0.0"
randombytes "^2.0.0"
ecc-jsbn@~0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=
dependencies:
jsbn "~0.1.0"
safer-buffer "^2.1.0"
eyes@0.1.x, eyes@~0.1.8:
version "0.1.8"
resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0"
integrity sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==
integrity sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
glob@7.2.3, "glob@>= 3.1.4", glob@^7.1.3:
getpass@^0.1.1:
version "0.1.7"
resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=
dependencies:
assert-plus "^1.0.0"
"glob@>= 3.1.4":
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^7.1.3:
version "7.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
@@ -116,7 +203,7 @@ inherits@2:
ipv6@*:
version "3.1.3"
resolved "https://registry.yarnpkg.com/ipv6/-/ipv6-3.1.3.tgz#4d9064f9c2dafa0dd10b8b7d76ffca4aad31b3b9"
integrity sha512-TmLbUIURMAZ161GZDddTtAAb3aceRNLn7PRmP8fANp8xDRCW9oIQva8eenA48bRvw347jBqSREXMI38DybbUiQ==
integrity sha1-TZBk+cLa+g3RC4t9dv/KSq0xs7k=
dependencies:
cli "0.4.x"
cliff "0.1.x"
@@ -125,7 +212,27 @@ ipv6@*:
isstream@0.1.x:
version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
jsbn@~0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
miller-rabin@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d"
integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==
dependencies:
bn.js "^4.0.0"
brorand "^1.0.1"
minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
dependencies:
brace-expansion "^1.1.7"
minimatch@^3.1.1:
version "3.1.2"
@@ -149,7 +256,14 @@ path-is-absolute@^1.0.0:
pkginfo@0.3.x:
version "0.3.1"
resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.3.1.tgz#5b29f6a81f70717142e09e765bbeab97b4f81e21"
integrity sha512-yO5feByMzAp96LtP58wvPKSbaKAi/1C4kV9XpTctr6EepnP6F33RBNOiVrdz9BrPA98U2BMFsTNHo44TWcbQ2A==
integrity sha1-Wyn2qB9wcXFC4J52W76rl7T4HiE=
randombytes@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
dependencies:
safe-buffer "^5.1.0"
rimraf@^3.0.0:
version "3.0.2"
@@ -163,15 +277,39 @@ run-script-os@^1.1.3:
resolved "https://registry.yarnpkg.com/run-script-os/-/run-script-os-1.1.6.tgz#8b0177fb1b54c99a670f95c7fdc54f18b9c72347"
integrity sha512-ql6P2LzhBTTDfzKts+Qo4H94VUKpxKDFz6QxxwaUZN0mwvi7L3lpOI7BqPCq7lgDh3XLl0dpeXwfcVIitlrYrw==
safe-buffer@^5.1.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sprintf@0.1.x:
version "0.1.5"
resolved "https://registry.yarnpkg.com/sprintf/-/sprintf-0.1.5.tgz#8f83e39a9317c1a502cb7db8050e51c679f6edcf"
integrity sha512-4X5KsuXFQ7f+d7Y+bi4qSb6eI+YoifDTGr0MQJXRoYO7BO7evfRCjds6kk3z7l5CiJYxgDN1x5Er4WiyCt+zTQ==
integrity sha1-j4PjmpMXwaUCy324BQ5Rxnn27c8=
sshpk@Eugeny/node-sshpk#c2b71d1243714d2daf0988f84c3323d180817136:
version "1.18.0"
resolved "https://codeload.github.com/Eugeny/node-sshpk/tar.gz/c2b71d1243714d2daf0988f84c3323d180817136"
dependencies:
asn1 "~0.2.3"
assert-plus "^1.0.0"
bcrypt-pbkdf "^1.0.0"
dashdash "^1.12.0"
ecc-jsbn "~0.1.1"
getpass "^0.1.1"
jsbn "~0.1.0"
safer-buffer "^2.0.2"
tweetnacl "~0.14.0"
stack-trace@0.0.x:
version "0.0.10"
resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0"
integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==
integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=
strip-ansi@^7.0.0:
version "7.1.0"
@@ -194,10 +332,15 @@ tmp@^0.2.0:
dependencies:
rimraf "^3.0.0"
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
version "0.14.5"
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
winston@0.8.x:
version "0.8.3"
resolved "https://registry.yarnpkg.com/winston/-/winston-0.8.3.tgz#64b6abf4cd01adcaefd5009393b1d8e8bec19db0"
integrity sha512-fPoamsHq8leJ62D1M9V/f15mjQ1UHe4+7j1wpAT3fqgA5JqhJkk4aIfPEjfMTI9x6ZTjaLOpMAjluLtmgO5b6g==
integrity sha1-ZLar9M0Brcrv1QCTk7HY6L7BnbA=
dependencies:
async "0.2.x"
colors "0.6.x"

View File

@@ -13,7 +13,6 @@ import { ResizeEvent, BaseTerminalProfile } from './interfaces'
import { TerminalDecorator } from './decorator'
import { SearchPanelComponent } from '../components/searchPanel.component'
import { MultifocusService } from '../services/multifocus.service'
import { getTerminalBackgroundColor } from '../helpers'
const INACTIVE_TAB_UNLOAD_DELAY = 1000 * 30
@@ -576,7 +575,14 @@ export class BaseTerminalTabComponent<P extends BaseTerminalProfile> extends Bas
configure (): void {
this.frontend?.configure(this.profile)
this.backgroundColor = getTerminalBackgroundColor(this.config, this.themes, this.profile.terminalColorScheme)
if (!this.themes.findCurrentTheme().followsColorScheme && this.config.store.terminal.background === 'colorScheme') {
const scheme = this.profile.terminalColorScheme ?? this.config.store.terminal.colorScheme
if (scheme.background) {
this.backgroundColor = scheme.background
}
} else {
this.backgroundColor = null
}
}
zoomIn (): void {

View File

@@ -3,7 +3,6 @@
width: 600px;
max-width: 80vw;
right: 40px;
height: 36px;
z-index: 5;
border-radius: 0 0 5px 5px;
background: rgba(0, 0, 0, .95);
@@ -23,6 +22,6 @@
opacity: .5;
}
:host ::ng-deep svg {
::ng-deep svg {
height: 16px;
}

View File

@@ -1,4 +1,3 @@
import deepEqual from 'deep-equal'
import { BehaviorSubject, filter, firstValueFrom, takeUntil } from 'rxjs'
import { Injector } from '@angular/core'
import { ConfigService, getCSSFontFamily, getWindows10Build, HostAppService, HotkeysService, Platform, PlatformService, ThemesService } from 'tabby-core'
@@ -12,9 +11,9 @@ import { Unicode11Addon } from '@xterm/addon-unicode11'
import { SerializeAddon } from '@xterm/addon-serialize'
import { ImageAddon } from '@xterm/addon-image'
import { CanvasAddon } from '@xterm/addon-canvas'
import { BaseTerminalProfile, TerminalColorScheme } from '../api/interfaces'
import { getTerminalBackgroundColor } from '../helpers'
import './xterm.css'
import deepEqual from 'deep-equal'
import { BaseTerminalProfile, TerminalColorScheme } from '../api/interfaces'
const COLOR_NAMES = [
'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white',
@@ -362,21 +361,21 @@ export class XTermFrontend extends Frontend {
}
private configureColors (scheme: TerminalColorScheme|undefined): void {
const appColorScheme = this.themes._getActiveColorScheme() as TerminalColorScheme
const config = this.configService.store
scheme = scheme ?? appColorScheme
scheme = scheme ?? this.themes._getActiveColorScheme()
const theme: ITheme = {
foreground: scheme.foreground,
selectionBackground: scheme.selection ?? '#88888888',
selectionForeground: scheme.selectionForeground ?? undefined,
background: getTerminalBackgroundColor(this.configService, this.themes, scheme) ?? '#00000000',
cursor: scheme.cursor,
cursorAccent: scheme.cursorAccent,
foreground: scheme!.foreground,
selectionBackground: scheme!.selection ?? '#88888888',
selectionForeground: scheme!.selectionForeground ?? undefined,
background: !this.themes.findCurrentTheme().followsColorScheme && config.terminal.background === 'colorScheme' ? scheme!.background : '#00000000',
cursor: scheme!.cursor,
cursorAccent: scheme!.cursorAccent,
}
for (let i = 0; i < COLOR_NAMES.length; i++) {
theme[COLOR_NAMES[i]] = scheme.colors[i]
theme[COLOR_NAMES[i]] = scheme!.colors[i]
}
if (!deepEqual(this.configuredTheme, theme)) {
@@ -400,10 +399,6 @@ export class XTermFrontend extends Frontend {
}
})
this.xtermCore.browser.isWindows = this.hostApp.platform === Platform.Windows
this.xtermCore.browser.isLinux = this.hostApp.platform === Platform.Linux
this.xtermCore.browser.isMac = this.hostApp.platform === Platform.macOS
this.xterm.options.fontFamily = getCSSFontFamily(config)
this.xterm.options.cursorStyle = {
beam: 'bar',

View File

@@ -1,21 +0,0 @@
import { TerminalColorScheme } from './api/interfaces'
import { ConfigService, ThemesService } from 'tabby-core'
export function getTerminalBackgroundColor (
config: ConfigService,
themes: ThemesService,
scheme?: TerminalColorScheme,
): string|null {
const appTheme = themes.findCurrentTheme()
const appColorScheme = themes._getActiveColorScheme() as TerminalColorScheme
// Use non transparent background when:
// - legacy theme and user choses colorScheme based BG
// - or new theme but profile-specific scheme is used
const shouldUseCSBackground =
!appTheme.followsColorScheme && config.store.terminal.background === 'colorScheme'
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|| appTheme.followsColorScheme && scheme?.name !== appColorScheme.name
return shouldUseCSBackground && scheme ? scheme.background : null
}

View File

@@ -2,7 +2,7 @@ import '@vaadin/vaadin-context-menu'
import copyToClipboard from 'copy-text-to-clipboard'
import { Injectable, Inject } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { PlatformService, ClipboardContent, MenuItemOptions, MessageBoxOptions, MessageBoxResult, FileUpload, FileUploadOptions, FileDownload, HTMLFileUpload, DirectoryUpload } from 'tabby-core'
import { PlatformService, ClipboardContent, MenuItemOptions, MessageBoxOptions, MessageBoxResult, FileUpload, FileUploadOptions, FileDownload, HTMLFileUpload } from 'tabby-core'
// eslint-disable-next-line no-duplicate-imports
import type { ContextMenuElement, ContextMenuItem } from '@vaadin/vaadin-context-menu'
@@ -135,10 +135,6 @@ export class WebPlatformService extends PlatformService {
})
}
async startUploadDirectory (_paths?: string[]): Promise<DirectoryUpload> {
return new DirectoryUpload()
}
setErrorHandler (handler: (_: any) => void): void {
window.addEventListener('error', handler)
}
@@ -149,7 +145,7 @@ export class WebPlatformService extends PlatformService {
}
class HTMLFileDownload extends FileDownload {
private buffers: Uint8Array[] = []
private buffers: Buffer[] = []
constructor (
private name: string,
@@ -171,8 +167,8 @@ class HTMLFileDownload extends FileDownload {
return this.size
}
async write (buffer: Uint8Array): Promise<void> {
this.buffers.push(Uint8Array.from(buffer))
async write (buffer: Buffer): Promise<void> {
this.buffers.push(Buffer.from(buffer))
this.increaseProgress(buffer.length)
if (this.isComplete()) {
this.finish()

View File

@@ -157,7 +157,6 @@ export default options => {
'os',
'path',
'readline',
'russh',
'@luminati-io/socksv5',
'stream',
'windows-native-registry',

View File

@@ -863,12 +863,10 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.1.tgz#e8a83f1aa8b649377bb1fb5d7bac5cb90e784dfe"
integrity sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==
"@types/node@^20.9.0":
version "20.14.14"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.14.tgz#6b655d4a88623b0edb98300bb9dd2107225f885e"
integrity sha512-d64f00982fS9YoOgJkAMolK7MN8Iq3TDdVjchbYHdEmjth/DHowx82GnoA+tVUAN+7vxfYUgAzi+JXbKNd2SDQ==
dependencies:
undici-types "~5.26.4"
"@types/node@^18.11.18":
version "18.17.12"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.17.12.tgz#c6bd7413a13e6ad9cfb7e97dd5c4e904c1821e50"
integrity sha512-d6xjC9fJ/nSnfDeU0AMDsaJyb1iHsqCSOdi84w4u+SlN/UgQdY5tRhpMzaFYsI4mnpvgTivEaQd0yOUhAtOnEQ==
"@types/parse5@^5":
version "5.0.3"
@@ -1553,6 +1551,13 @@ asn1.js@^5.2.0:
minimalistic-assert "^1.0.0"
safer-buffer "^2.1.0"
asn1@^0.2.6:
version "0.2.6"
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d"
integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==
dependencies:
safer-buffer "~2.1.0"
asn1@~0.2.3:
version "0.2.4"
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
@@ -1687,7 +1692,7 @@ base64-js@^1.3.1, base64-js@^1.5.1:
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
bcrypt-pbkdf@^1.0.0:
bcrypt-pbkdf@^1.0.0, bcrypt-pbkdf@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=
@@ -1907,6 +1912,11 @@ buffer@^5.1.0:
base64-js "^1.3.1"
ieee754 "^1.1.13"
buildcheck@~0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/buildcheck/-/buildcheck-0.0.6.tgz#89aa6e417cfd1e2196e3f8fe915eb709d2fe4238"
integrity sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==
builder-util-runtime@9.2.1:
version "9.2.1"
resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-9.2.1.tgz#3184dcdf7ed6c47afb8df733813224ced4f624fd"
@@ -2495,6 +2505,14 @@ core-util-is@1.0.2, core-util-is@~1.0.0:
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
cpu-features@~0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/cpu-features/-/cpu-features-0.0.8.tgz#a2d464b023b8ad09004c8cdca23b33f192f63546"
integrity sha512-BbHBvtYhUhksqTjr6bhNOjGgMnhwhGTQmOoZGD+K7BCaQDCuZl/Ve1ZxUSMRwVC4D/rkCPQ2MAIeYzrWyK7eEg==
dependencies:
buildcheck "~0.0.6"
nan "^2.17.0"
crc@^3.8.0:
version "3.8.0"
resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6"
@@ -3053,13 +3071,13 @@ electron-to-chromium@^1.4.284:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.286.tgz#0e039de59135f44ab9a8ec9025e53a9135eba11f"
integrity sha512-Vp3CVhmYpgf4iXNKAucoQUDcCrBQX3XLBtwgFqP9BUXuucgvAV9zWp1kYU7LL9j4++s9O+12cb3wMtN4SJy6UQ==
electron@^29:
version "29.4.5"
resolved "https://registry.yarnpkg.com/electron/-/electron-29.4.5.tgz#b83bbeee6fc722dbbaab30d3a6bc8e982c9ab98d"
integrity sha512-DlEuzGbWBYl1Qr0qUYgNZdoixJg4YGHy2HC6fkRjSXSlb01UrQ5ORi8hNLzelzyYx8rNQyyE3zDUuk9EnZwYuA==
electron@^27.0.4:
version "27.1.0"
resolved "https://registry.yarnpkg.com/electron/-/electron-27.1.0.tgz#d759885e552d7d926526cfc433ab312796f74a9a"
integrity sha512-XPdJiO475QJ8cx59/goWNNWnlV0vab+Ut3occymos7VDxkHV5mFrlW6tcGi+M3bW6gBfwpJocWMng8tw542vww==
dependencies:
"@electron/get" "^2.0.0"
"@types/node" "^20.9.0"
"@types/node" "^18.11.18"
extract-zip "^2.0.1"
elliptic@^6.5.3:
@@ -5603,10 +5621,10 @@ lzma-native@^8.0.5, lzma-native@^8.0.6:
node-gyp-build "^4.2.1"
readable-stream "^3.6.0"
macos-release@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-3.3.0.tgz#92cb67bc66d67c3fde4a9e14f5f909afa418b072"
integrity sha512-tPJQ1HeyiU2vRruNGhZ+VleWuMQRro8iFtJxYgnS4NQe+EukKF6aGiIT+7flZhISAt2iaXBCfFGvAyif7/f8nQ==
macos-release@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-3.1.0.tgz#6165bb0736ae567ed6649e36ce6a24d87cbb7aca"
integrity sha512-/M/R0gCDgM+Cv1IuBG1XGdfTFnMEG6PZeT+KGWHO/OG+imqmaD9CH5vHBTycEM3+Kc4uG2Il+tFAuUWLqQOeUA==
magic-string@^0.27.0:
version "0.27.0"
@@ -5965,7 +5983,7 @@ mute-stream@~0.0.4:
resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz"
integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=
nan@2.17.0:
nan@2.17.0, nan@^2.17.0:
version "2.17.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb"
integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==
@@ -6010,10 +6028,10 @@ no-case@^3.0.4:
lower-case "^2.0.2"
tslib "^2.0.3"
node-abi@^3.0.0, node-abi@^3.65.0:
version "3.65.0"
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.65.0.tgz#ca92d559388e1e9cab1680a18c1a18757cdac9d3"
integrity sha512-ThjYBfoDNr08AWx6hGaRbfPwxKV9kVzAzOzlLKbk2CuqXE2xnCh+cbAGnwM3t8Lq4v9rUB7VfondlkBckcJrVA==
node-abi@^3.0.0, node-abi@^3.51.0:
version "3.51.0"
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.51.0.tgz#970bf595ef5a26a271307f8a4befa02823d4e87d"
integrity sha512-SQkEP4hmNWjlniS5zdnfIXTk1x7Ome85RDzHlTbBtzE97Gfwz/Ipw4v/Ryk20DWIy3yCNVLVlGKApCnmvYoJbA==
dependencies:
semver "^7.3.5"
@@ -8200,6 +8218,17 @@ sprintf-js@~1.0.2:
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
ssh2@^1.14.0:
version "1.14.0"
resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-1.14.0.tgz#8f68440e1b768b66942c9e4e4620b2725b3555bb"
integrity sha512-AqzD1UCqit8tbOKoj6ztDDi1ffJZ2rV2SwlgrVVrHPkV5vWqGJOVp5pmtj18PunkPJAuKQsnInyKV+/Nb2bUnA==
dependencies:
asn1 "^0.2.6"
bcrypt-pbkdf "^1.0.2"
optionalDependencies:
cpu-features "~0.0.8"
nan "^2.17.0"
sshpk@^1.7.0:
version "1.16.1"
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
@@ -8897,11 +8926,6 @@ unbox-primitive@^1.0.2:
has-symbols "^1.0.3"
which-boxed-primitive "^1.0.2"
undici-types@~5.26.4:
version "5.26.5"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
unique-filename@^1.1.0, unique-filename@~1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.0.tgz"