Compare commits

..

9 Commits

Author SHA1 Message Date
allcontributors[bot]
008cae9710 update README.pt-BR.md [skip ci] 2024-07-10 08:52:38 +00:00
allcontributors[bot]
d38a900553 update README.id-ID.md [skip ci] 2024-07-10 08:51:34 +00:00
allcontributors[bot]
964cd482b2 update .all-contributorsrc [skip ci] 2024-07-10 08:51:30 +00:00
allcontributors[bot]
c127a00016 update README.de-DE.md [skip ci] 2024-07-10 08:50:54 +00:00
allcontributors[bot]
e857da28ac update README.it-IT.md [skip ci] 2024-07-10 08:50:53 +00:00
allcontributors[bot]
06b625cb6f update README.ko-KR.md [skip ci] 2024-07-10 08:50:52 +00:00
allcontributors[bot]
8e8cf6a500 update README.ru-RU.md [skip ci] 2024-07-10 08:50:51 +00:00
allcontributors[bot]
4930d0fac6 update README.zh-CN.md [skip ci] 2024-07-10 08:50:50 +00:00
allcontributors[bot]
ba31e14fa4 update README.md [skip ci] 2024-07-10 08:50:49 +00:00
91 changed files with 1491 additions and 1267 deletions

View File

@@ -1310,24 +1310,6 @@
"contributions": [ "contributions": [
"code" "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, "contributorsPerLine": 7,

View File

@@ -13,11 +13,11 @@ jobs:
- name: Installing Node - name: Installing Node
uses: actions/setup-node@v3.7.0 uses: actions/setup-node@v3.7.0
with: with:
node-version: 18 node-version: 16
- name: Install deps - name: Install deps
run: | run: |
npm i -g yarn npm i -g yarn@1.19.1
cd app cd app
yarn yarn
cd .. cd ..
@@ -37,15 +37,9 @@ jobs:
matrix: matrix:
include: include:
- arch: x86_64 - arch: x86_64
rust_triple: x86_64-apple-darwin
- arch: arm64 - arch: arm64
rust_triple: aarch64-apple-darwin
fail-fast: false fail-fast: false
env:
ARCH: ${{matrix.arch}}
RUST_TARGET_TRIPLE: ${{matrix.rust_triple}}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
@@ -55,18 +49,23 @@ jobs:
- name: Installing Node - name: Installing Node
uses: actions/setup-node@v3.7.0 uses: actions/setup-node@v3.7.0
with: with:
node-version: 18 node-version: 16
- run: rustup target add ${{matrix.rust_triple}}
- name: Install deps - name: Install deps
run: | run: |
sudo -H pip3 install setuptools 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 yarn --network-timeout 1000000
env: env:
ARCH: ${{matrix.arch}} 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 - name: Webpack
run: yarn run build run: yarn run build
@@ -138,24 +137,18 @@ jobs:
include: include:
- build-arch: x64 - build-arch: x64
arch: amd64 arch: amd64
rust_triple: x86_64-unknown-linux-gnu
- build-arch: arm64 - build-arch: arm64
arch: arm64 arch: arm64
rust_triple: aarch64-unknown-linux-gnu
triplet: aarch64-linux-gnu- triplet: aarch64-linux-gnu-
- build-arch: arm - build-arch: arm
arch: armhf arch: armhf
rust_triple: arm-unknown-linux-gnueabihf
triplet: arm-linux-gnueabihf- triplet: arm-linux-gnueabihf-
fail-fast: false
env: env:
CC: ${{matrix.triplet}}gcc CC: ${{matrix.triplet}}gcc
CXX: ${{matrix.triplet}}g++ CXX: ${{matrix.triplet}}g++
ARCH: ${{matrix.build-arch}} ARCH: ${{matrix.build-arch}}
npm_config_arch: ${{matrix.build-arch}} npm_config_arch: ${{matrix.build-arch}}
npm_config_target_arch: ${{matrix.build-arch}} npm_config_target_arch: ${{matrix.build-arch}}
RUST_TARGET_TRIPLE: ${{matrix.rust_triple}}
steps: steps:
- name: Checkout - name: Checkout
@@ -168,51 +161,52 @@ jobs:
with: with:
node-version: 18 node-version: 18
- run: rustup target add ${{matrix.rust_triple}} - name: Install deps (amd64)
- name: Install dependencies
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install libarchive-tools zsh crossbuild-essential-${{matrix.arch}} sudo apt-get install libarchive-tools zsh python3-distutils
- 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'
- name: Install npm_modules (amd64) - name: Install npm_modules (amd64)
run: | run: |
npm i -g yarn node-gyp npm i -g yarn
yarn --network-timeout 1000000 --arch=${{matrix.build-arch}} --target-arch=${{matrix.build-arch}} 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}}) - name: Webpack (${{matrix.arch}})
run: yarn run build --arch=${{matrix.build-arch}} --target_arch=${{matrix.build-arch}} run: yarn run build --arch=${{matrix.build-arch}} --target_arch=${{matrix.build-arch}}
@@ -228,10 +222,57 @@ jobs:
USE_HARD_LINKS: false USE_HARD_LINKS: false
# DEBUG: electron-builder,electron-builder:* # 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' run: zsh -c 'tar czf tabby-web.tar.gz (tabby-*|web)/dist'
if: matrix.build-arch == 'x64' 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) - name: Upload symbols (amd64 only)
run: | run: |
sudo npm install -g @sentry/cli --unsafe-perm sudo npm install -g @sentry/cli --unsafe-perm
@@ -243,15 +284,13 @@ jobs:
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
- name: Upload packages to packagecloud.io - 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/') if: github.repository == 'Eugeny/tabby' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
env: env:
PACKAGECLOUD_TOKEN: ${{ secrets.PACKAGECLOUD_TOKEN }} PACKAGECLOUD_TOKEN: ${{ secrets.PACKAGECLOUD_TOKEN }}
with: with:
repo: 'eugeny/tabby' repo: 'eugeny/tabby'
dir: 'dist' 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 - uses: actions/upload-artifact@master
name: Upload AppImage (${{matrix.arch}}) name: Upload AppImage (${{matrix.arch}})
@@ -290,22 +329,17 @@ jobs:
path: tabby-web.tar.gz path: tabby-web.tar.gz
if: matrix.build-arch == 'x64' if: matrix.build-arch == 'x64'
Windows-Build: Windows-Build:
runs-on: windows-latest runs-on: windows-2022
needs: Lint needs: Lint
strategy: strategy:
matrix: matrix:
include: include:
- arch: x64 - arch: x64
rust_triple: x86_64-pc-windows-msvc
- arch: arm64 - arch: arm64
rust_triple: aarch64-pc-windows-msvc
fail-fast: false fail-fast: false
env:
RUST_TARGET_TRIPLE: ${{matrix.rust_triple}}
ARCH: ${{matrix.arch}}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
@@ -315,20 +349,17 @@ jobs:
- name: Installing Node - name: Installing Node
uses: actions/setup-node@v3.7.0 uses: actions/setup-node@v3.7.0
with: with:
node-version: 18 node-version: 16
- run: npm i -g npx
- run: rustup target add ${{matrix.rust_triple}}
- name: Update node-gyp - name: Update node-gyp
run: | 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"} npm prefix -g | % {npm config set node_gyp "$_\node_modules\node-gyp\bin\node-gyp.js"}
- name: Build - name: Build
shell: powershell shell: powershell
run: | run: |
npm i -g yar node-gyp npm i -g yarn@1.19.1
yarn --network-timeout 1000000 yarn --network-timeout 1000000
yarn run build yarn run build
node scripts/prepackage-plugins.mjs node scripts/prepackage-plugins.mjs
@@ -348,7 +379,7 @@ jobs:
- name: Build packages without signing - name: Build packages without signing
run: node scripts/build-windows.mjs 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: env:
ARCH: ${{matrix.arch}} ARCH: ${{matrix.arch}}

1
.gitignore vendored
View File

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

View File

@@ -340,8 +340,6 @@ Dank geht an diese wunderbaren Menschen ([emoji key](https://allcontributors.org
<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://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://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/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> </tr>
</tbody> </tbody>
</table> </table>

View File

@@ -341,9 +341,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://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://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> </tr>
</tbody> </tbody>
</table> </table>

View File

@@ -339,8 +339,6 @@ Terima kasih kepada mereka yang telah membantu ([emoji key](https://allcontribut
<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://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://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/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> </tr>
</tbody> </tbody>
</table> </table>

View File

@@ -335,8 +335,6 @@ Grazie a queste persone meravigliose ([emoji key](https://allcontributors.org/do
<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://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://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/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> </tr>
</tbody> </tbody>
</table> </table>

View File

@@ -349,9 +349,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://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://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> </tr>
</tbody> </tbody>
</table> </table>

View File

@@ -334,8 +334,6 @@ Pull requests and plugins are welcome!
<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://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://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/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> </tr>
</tbody> </tbody>
</table> </table>

View File

@@ -2,7 +2,7 @@
<p align="center"> <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>
<p align="center"> <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. > 👋 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: ### 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 [**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> <a name="contributing"></a>
# Contributing # Contributing
@@ -358,8 +356,6 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<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://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://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/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> </tr>
</tbody> </tbody>
</table> </table>

View File

@@ -343,8 +343,6 @@ Obrigado vai para essas pessoas maravilhosas ([emoji key](https://allcontributor
<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://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://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/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> </tr>
</tbody> </tbody>
</table> </table>

View File

@@ -335,8 +335,6 @@ Pull-запросы и плагины приветствуются!
<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://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://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/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> </tr>
</tbody> </tbody>
</table> </table>

View File

@@ -334,8 +334,6 @@
<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://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://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/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> </tr>
</tbody> </tbody>
</table> </table>

View File

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

View File

@@ -1,5 +1,5 @@
import * as glasstron from 'glasstron' import * as glasstron from 'glasstron'
import { autoUpdater } from 'electron-updater'
import { Subject, Observable, debounceTime } from 'rxjs' import { Subject, Observable, debounceTime } from 'rxjs'
import { BrowserWindow, app, ipcMain, Rectangle, Menu, screen, BrowserWindowConstructorOptions, TouchBar, nativeImage, WebContents } from 'electron' import { BrowserWindow, app, ipcMain, Rectangle, Menu, screen, BrowserWindowConstructorOptions, TouchBar, nativeImage, WebContents } from 'electron'
import ElectronConfig = require('electron-config') import ElectronConfig = require('electron-config')
@@ -26,7 +26,7 @@ abstract class GlasstronWindow extends BrowserWindow {
abstract setBlur (_: boolean) 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`) const activityIcon = nativeImage.createFromPath(`${app.getAppPath()}/assets/activity.png`)
@@ -159,7 +159,6 @@ export class Window {
} }
this.setupWindowManagement() this.setupWindowManagement()
this.setupUpdater()
this.ready = new Promise(resolve => { this.ready = new Promise(resolve => {
const listener = event => { const listener = event => {
@@ -347,8 +346,11 @@ export class Window {
this.send('host:window-focused') this.send('host:window-focused')
}) })
this.on('ready', () => { ipcMain.on('ready', event => {
this.window?.webContents.send('start', { if (!this.window || event.sender !== this.window.webContents) {
return
}
this.window.webContents.send('start', {
config: this.configStore, config: this.configStore,
executable: app.getPath('exe'), executable: app.getPath('exe'),
windowID: this.window.id, windowID: this.window.id,
@@ -357,26 +359,42 @@ export class Window {
}) })
}) })
this.on('window-minimize', () => { ipcMain.on('window-minimize', event => {
this.window?.minimize() if (!this.window || event.sender !== this.window.webContents) {
return
}
this.window.minimize()
}) })
this.on('window-set-bounds', (_, bounds) => { ipcMain.on('window-set-bounds', (event, bounds) => {
this.window?.setBounds(bounds) if (!this.window || event.sender !== this.window.webContents) {
return
}
this.window.setBounds(bounds)
}) })
this.on('window-set-always-on-top', (_, flag) => { ipcMain.on('window-set-always-on-top', (event, flag) => {
this.window?.setAlwaysOnTop(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.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') { if (process.platform === 'win32') {
const symbolColor: string = theme.foreground const symbolColor: string = theme.foreground
this.window?.setTitleBarOverlay( this.window.setTitleBarOverlay(
{ {
symbolColor: symbolColor, symbolColor: symbolColor,
height: 32, height: 32,
@@ -385,23 +403,32 @@ export class Window {
} }
}) })
this.on('window-set-title', (_, title) => { ipcMain.on('window-set-title', (event, title) => {
this.window?.setTitle(title) if (!this.window || event.sender !== this.window.webContents) {
return
}
this.window.setTitle(title)
}) })
this.on('window-bring-to-front', () => { ipcMain.on('window-bring-to-front', event => {
if (this.window?.isMinimized()) { if (!this.window || event.sender !== this.window.webContents) {
return
}
if (this.window.isMinimized()) {
this.window.restore() this.window.restore()
} }
this.present() this.present()
}) })
this.on('window-close', () => { ipcMain.on('window-close', event => {
if (!this.window || event.sender !== this.window.webContents) {
return
}
this.closing = true this.closing = true
this.window.close() 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 => ({ this.touchBarControl.segments = segments.map(s => ({
label: s.label, label: s.label,
icon: s.hasActivity ? activityIcon : undefined, icon: s.hasActivity ? activityIcon : undefined,
@@ -441,46 +468,8 @@ export class Window {
this.window.setOpacity(opacity) this.window.setOpacity(opacity)
}) })
this.on('window-set-progress-bar', (_, value) => { ipcMain.on('window-set-progress-bar', (_event, value) => {
this.window?.setProgressBar(value, { mode: value < 0 ? 'none' : 'normal' }) 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()
}) })
} }

View File

@@ -15,8 +15,8 @@
"watch": "webpack --progress --color --watch" "watch": "webpack --progress --color --watch"
}, },
"dependencies": { "dependencies": {
"@electron/remote": "^2", "@electron/remote": "2.0.10",
"node-pty": "^1.1.0-beta.14", "node-pty": "^1.0",
"any-promise": "^1.3.0", "any-promise": "^1.3.0",
"electron-config": "2.0.0", "electron-config": "2.0.0",
"electron-debug": "^3.2.0", "electron-debug": "^3.2.0",
@@ -24,13 +24,13 @@
"electron-updater": "^5.2.1", "electron-updater": "^5.2.1",
"fontmanager-redux": "1.1.0", "fontmanager-redux": "1.1.0",
"glasstron": "0.1.1", "glasstron": "0.1.1",
"node-powershell": "5.0.1",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"keytar": "^7.9.0", "keytar": "^7.9.0",
"mz": "^2.7.0", "mz": "^2.7.0",
"native-process-working-directory": "^1.0.2", "native-process-working-directory": "^1.0.2",
"npm": "6", "npm": "6",
"rxjs": "^7.5.7", "rxjs": "^7.5.7",
"russh": "0.0.3",
"source-map-support": "^0.5.20", "source-map-support": "^0.5.20",
"v8-compile-cache": "^2.3.0", "v8-compile-cache": "^2.3.0",
"yargs": "^17.7.2" "yargs": "^17.7.2"
@@ -64,7 +64,8 @@
"tabby-terminal": "*" "tabby-terminal": "*"
}, },
"resolutions": { "resolutions": {
"*/node-abi": "^3", "*/node-abi": "^2.20.0",
"node-gyp": "^10.0.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-config': 'commonjs electron-config',
'electron-debug': 'commonjs electron-debug', 'electron-debug': 'commonjs electron-debug',
'electron-promise-ipc': 'commonjs electron-promise-ipc', 'electron-promise-ipc': 'commonjs electron-promise-ipc',
'electron-updater': 'commonjs electron-updater',
fs: 'commonjs fs', fs: 'commonjs fs',
glasstron: 'commonjs glasstron', glasstron: 'commonjs glasstron',
mz: 'commonjs mz', mz: 'commonjs mz',

View File

@@ -2,10 +2,10 @@
# yarn lockfile v1 # yarn lockfile v1
"@electron/remote@^2": "@electron/remote@2.0.10":
version "2.1.2" version "2.0.10"
resolved "https://registry.yarnpkg.com/@electron/remote/-/remote-2.1.2.tgz#52a97c8faa5b769155b649ef262f2f8c851776e6" resolved "https://registry.yarnpkg.com/@electron/remote/-/remote-2.0.10.tgz#133e2f607b1861ac249bd78b5abd1e961feed713"
integrity sha512-EPwNx+nhdrTBxyCqXt/pftoQg/ybtWDW3DUWHafejvnB1ZGGfMpv6e15D8KeempocjXe78T7WreyGGb3mlZxdA== integrity sha512-3SFKKaQXcyWgwmibud+UqJl/XlHOgLcI3fwtB9pNelPSJAcTxocOJrF6FaxBIQaj1+R05Di6xuAswZpXAW7xhA==
"@iarna/cli@^1.2.0": "@iarna/cli@^1.2.0":
version "1.2.0" version "1.2.0"
@@ -28,11 +28,6 @@
wrap-ansi "^8.1.0" wrap-ansi "^8.1.0"
wrap-ansi-cjs "npm:wrap-ansi@^7.0.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": "@ngx-translate/core@^14.0.0":
version "14.0.0" version "14.0.0"
resolved "https://registry.yarnpkg.com/@ngx-translate/core/-/core-14.0.0.tgz#af421d0e1a28376843f0fed375cd2fae7630a5ff" 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" x11 "^2.3.0"
glob@^10.2.2, glob@^10.3.10: glob@^10.2.2, glob@^10.3.10:
version "10.4.5" version "10.3.10"
resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.10.tgz#0351ebb809fd187fe421ab96af83d3a70715df4b"
integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== integrity sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==
dependencies: dependencies:
foreground-child "^3.1.0" foreground-child "^3.1.0"
jackspeak "^3.1.2" jackspeak "^2.3.5"
minimatch "^9.0.4" minimatch "^9.0.1"
minipass "^7.1.2" minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
package-json-from-dist "^1.0.0" path-scurry "^1.10.1"
path-scurry "^1.11.1"
glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
version "7.2.3" version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" resolved "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz"
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
dependencies: dependencies:
fs.realpath "^1.0.0" fs.realpath "^1.0.0"
inflight "^1.0.4" inflight "^1.0.4"
inherits "2" inherits "2"
minimatch "^3.1.1" minimatch "^3.0.4"
once "^1.3.0" once "^1.3.0"
path-is-absolute "^1.0.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" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz"
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
jackspeak@^3.1.2: jackspeak@^2.3.5:
version "3.4.3" version "2.3.6"
resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8"
integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==
dependencies: dependencies:
"@isaacs/cliui" "^8.0.2" "@isaacs/cliui" "^8.0.2"
optionalDependencies: optionalDependencies:
@@ -2292,18 +2286,13 @@ lowercase-keys@^1.0.0:
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== 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" version "10.0.2"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.2.tgz#34504678cc3266b09b8dfd6fab4e1515258271b7" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.2.tgz#34504678cc3266b09b8dfd6fab4e1515258271b7"
integrity sha512-Yj9mA8fPiVgOUpByoTZO5pNrcl5Yk37FcSHsUINpAsaBIEZIuqcCclDZJCVxqQShDsmYX8QG63svJiTbOATZwg== integrity sha512-Yj9mA8fPiVgOUpByoTZO5pNrcl5Yk37FcSHsUINpAsaBIEZIuqcCclDZJCVxqQShDsmYX8QG63svJiTbOATZwg==
dependencies: dependencies:
semver "^7.3.5" 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: lru-cache@^4.0.1:
version "4.1.5" version "4.1.5"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
@@ -2423,17 +2412,10 @@ minimatch@^3.0.4:
dependencies: dependencies:
brace-expansion "^1.1.7" brace-expansion "^1.1.7"
minimatch@^3.1.1: minimatch@^9.0.1:
version "3.1.2" version "9.0.3"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825"
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==
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==
dependencies: dependencies:
brace-expansion "^2.0.1" 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" resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c"
integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ== 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: minizlib@^1.3.3:
version "1.3.3" version "1.3.3"
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" 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" object-assign "^4.0.1"
thenify-all "^1.0.0" thenify-all "^1.0.0"
nan@^2.17.0: nan@^2.17.0, "nan@github:jkleinsc/nan#remove_accessor_signature":
version "2.20.0" version "2.16.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.20.0.tgz#08c5ea813dd54ed16e5bd6505bf42af4f7838ca3" resolved "https://codeload.github.com/jkleinsc/nan/tar.gz/6a2f95a6a2209d8aa7542fb18099fd808a802059"
integrity sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==
napi-build-utils@^1.0.1: napi-build-utils@^1.0.1:
version "1.0.2" 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" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
node-abi@^3: node-abi@^2.20.0:
version "3.65.0" version "2.30.1"
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.65.0.tgz#ca92d559388e1e9cab1680a18c1a18757cdac9d3" resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.30.1.tgz#c437d4b1fe0e285aaf290d45b45d4d7afedac4cf"
integrity sha512-ThjYBfoDNr08AWx6hGaRbfPwxKV9kVzAzOzlLKbk2CuqXE2xnCh+cbAGnwM3t8Lq4v9rUB7VfondlkBckcJrVA== integrity sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w==
dependencies: dependencies:
semver "^7.3.5" semver "^5.4.1"
node-abi@^3.3.0: node-abi@^3.3.0:
version "3.8.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" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f"
integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ== 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: node-fetch-npm@^2.0.2:
version "2.0.4" version "2.0.4"
resolved "https://registry.yarnpkg.com/node-fetch-npm/-/node-fetch-npm-2.0.4.tgz#6507d0e17a9ec0be3bec516958a497cec54bf5a4" 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" tar "^6.1.2"
which "^4.0.0" which "^4.0.0"
node-pty@^1.1.0-beta.14: node-pty@^1.0:
version "1.1.0-beta9" version "1.0.0"
resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.1.0-beta9.tgz#ed643cb3b398d031b4e31c216e8f3b0042435f1d" resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.0.0.tgz#7daafc0aca1c4ca3de15c61330373af4af5861fd"
integrity sha512-/Ue38pvXJdgRZ3+me1FgfglLd301GhJN0NStiotdt61tm43N5htUyR/IXOUzOKuNaFmCwIhy6nwb77Ky41LMbw== integrity sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA==
dependencies: dependencies:
node-addon-api "^7.1.0" nan "^2.17.0"
nopt@^4.0.3: nopt@^4.0.3:
version "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" resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== 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: package-json@^4.0.0:
version "4.0.1" version "4.0.1"
resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed" 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" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
path-scurry@^1.11.1: path-scurry@^1.10.1:
version "1.11.1" version "1.10.1"
resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.1.tgz#9ba6bf5aa8500fe9fd67df4f0d9483b2b0bfc698"
integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== integrity sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==
dependencies: dependencies:
lru-cache "^10.2.0" lru-cache "^9.1.1 || ^10.0.0"
minipass "^5.0.0 || ^6.0.2 || ^7.0.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
path-type@^2.0.0: path-type@^2.0.0:
@@ -3636,13 +3602,6 @@ run-queue@^1.0.0, run-queue@^1.0.3:
dependencies: dependencies:
aproba "^1.1.1" 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: rxjs@^7.5.2, rxjs@^7.5.7:
version "7.5.7" version "7.5.7"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.7.tgz#2ec0d57fdc89ece220d2e702730ae8f1e49def39" 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= 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: "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" version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== 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== integrity sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.1: "strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.1:
name strip-ansi-cjs
version "6.0.1" version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -4475,7 +4432,6 @@ worker-farm@^1.6.0, worker-farm@^1.7.0:
errno "~0.1.7" errno "~0.1.7"
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
name wrap-ansi-cjs
version "7.0.0" version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== 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"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>com.apple.security.automation.apple-events</key>
<true/>
<key>com.apple.security.cs.allow-jit</key> <key>com.apple.security.cs.allow-jit</key>
<true/> <true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key> <key>com.apple.security.cs.allow-unsigned-executable-memory</key>

View File

@@ -51,7 +51,6 @@ mac:
entitlements: "./build/mac/entitlements.plist" entitlements: "./build/mac/entitlements.plist"
entitlementsInherit: "./build/mac/entitlements.plist" entitlementsInherit: "./build/mac/entitlements.plist"
extendInfo: extendInfo:
ElectronTeamID: ${teamId}
NSRequiresAquaSystemAppearance: false NSRequiresAquaSystemAppearance: false
NSCameraUsageDescription: "A subprocess requests access to the device's camera." NSCameraUsageDescription: "A subprocess requests access to the device's camera."
NSMicrophoneUsageDescription: "A subprocess requests access to the device's microphone." 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." NSDownloadsFolderUsageDescription: "A subprocess requests access to the user's Downloads folder."
NSNetworkVolumesUsageDescription: 'A subprocess requests access to files on a network volume.' NSNetworkVolumesUsageDescription: 'A subprocess requests access to files on a network volume.'
NSRemovableVolumesUsageDescription: 'A subprocess requests access to files on a removable 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: linux:
category: "Utility;TerminalEmulator;System" category: "Utility;TerminalEmulator;System"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n" "Project-Id-Version: tabby\n"
"Language-Team: Afrikaans\n" "Language-Team: Afrikaans\n"
"Language: af_ZA\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 #: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?" msgid "\"{command}\" is still running. Close?"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n" "Project-Id-Version: tabby\n"
"Language-Team: Bulgarian\n" "Language-Team: Bulgarian\n"
"Language: bg_BG\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 #: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?" msgid "\"{command}\" is still running. Close?"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n" "Project-Id-Version: tabby\n"
"Language-Team: Czech\n" "Language-Team: Czech\n"
"Language: cs_CZ\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 #: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?" msgid "\"{command}\" is still running. Close?"
@@ -1935,7 +1935,7 @@ msgstr "Sériové připojení"
#: tabby-serial/src/profiles.ts:73 #: tabby-serial/src/profiles.ts:73
msgid "Serial: {description}" 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/setVaultPassphraseModal.component.html:2
#: locale/tmp-html/tabby-settings/src/components/vaultSettingsTab.component.html:5 #: locale/tmp-html/tabby-settings/src/components/vaultSettingsTab.component.html:5

View File

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

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n" "Project-Id-Version: tabby\n"
"Language-Team: German\n" "Language-Team: German\n"
"Language: de_DE\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 #: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?" msgid "\"{command}\" is still running. Close?"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n" "Project-Id-Version: tabby\n"
"Language-Team: English, United Kingdom\n" "Language-Team: English, United Kingdom\n"
"Language: en_GB\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 #: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?" msgid "\"{command}\" is still running. Close?"
@@ -2036,6 +2036,10 @@ msgstr ""
msgid "Show Serial connections" msgid "Show Serial connections"
msgstr "" msgstr ""
#: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:73
msgid "Hide Tabby in tray or menu bar."
msgstr ""
#: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:152 #: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:152
msgid "Show tabs in fullscreen mode" msgid "Show tabs in fullscreen mode"
msgstr "" msgstr ""
@@ -2044,6 +2048,10 @@ msgstr ""
msgid "Show toolbar" msgid "Show toolbar"
msgstr "" msgstr ""
#: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:73
msgid "Hide tray"
msgstr ""
#: locale/tmp-html/tabby-settings/src/components/vaultSettingsTab.component.html:45 #: locale/tmp-html/tabby-settings/src/components/vaultSettingsTab.component.html:45
msgid "Show vault contents" msgid "Show vault contents"
msgstr "" msgstr ""

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n" "Project-Id-Version: tabby\n"
"Language-Team: Spanish\n" "Language-Team: Spanish\n"
"Language: es_ES\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 #: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?" msgid "\"{command}\" is still running. Close?"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n" "Project-Id-Version: tabby\n"
"Language-Team: French\n" "Language-Team: French\n"
"Language: fr_FR\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 #: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?" msgid "\"{command}\" is still running. Close?"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n" "Project-Id-Version: tabby\n"
"Language-Team: Croatian\n" "Language-Team: Croatian\n"
"Language: hr_HR\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 #: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?" msgid "\"{command}\" is still running. Close?"

View File

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

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n" "Project-Id-Version: tabby\n"
"Language-Team: Italian\n" "Language-Team: Italian\n"
"Language: it_IT\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 #: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?" msgid "\"{command}\" is still running. Close?"
@@ -31,7 +31,7 @@ msgstr "Annulla tutto"
#: locale/tmp-html/tabby-ssh/src/components/hostKeyPromptModal.component.html:24 #: locale/tmp-html/tabby-ssh/src/components/hostKeyPromptModal.component.html:24
msgid "Accept and remember key" 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 #: locale/tmp-html/tabby-ssh/src/components/hostKeyPromptModal.component.html:25
msgid "Accept just this once" msgid "Accept just this once"
@@ -43,7 +43,7 @@ msgstr "Accessibilità"
#: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:27 #: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:27
msgid "Acrylic background" 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/commandLineEditor.component.html:24
#: locale/tmp-html/tabby-local/src/components/environmentEditor.component.html:11 #: 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 #: tabby-terminal/src/services/multifocus.service.ts:19
msgid "Broadcast mode. Click anywhere to cancel." 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 #: locale/tmp-html/tabby-plugin-manager/src/components/pluginsSettingsTab.component.html:50
#: tabby-core/src/services/profiles.service.ts:411 #: 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 #: locale/tmp-html/tabby-terminal/src/components/colorSchemeSettingsTab.component.html:17
msgid "Dark mode" msgid "Dark mode"
msgstr "Tema scuro" msgstr "Modalità scura"
#: locale/tmp-html/tabby-serial/src/components/serialProfileSettings.component.html:26 #: locale/tmp-html/tabby-serial/src/components/serialProfileSettings.component.html:26
msgid "Data bits" msgid "Data bits"
@@ -707,7 +708,7 @@ msgstr "La selezione con doppio clic si fermerà a questi caratteri"
#: tabby-core/src/tabContextMenu.ts:79 #: tabby-core/src/tabContextMenu.ts:79
msgid "Down" msgid "Down"
msgstr "In basso" msgstr "Giù"
#: locale/tmp-html/tabby-settings/src/components/configSyncSettingsTab.component.html:43 #: locale/tmp-html/tabby-settings/src/components/configSyncSettingsTab.component.html:43
msgid "Download" msgid "Download"
@@ -895,7 +896,7 @@ msgstr "Evidenzia il riquadro a destra"
#: tabby-core/src/theme.ts:34 #: tabby-core/src/theme.ts:34
msgid "Follow the color scheme" 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 #: locale/tmp-html/tabby-terminal/src/components/appearanceSettingsTab.component.html:5
msgid "Font" 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 #: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:76
#: tabby-core/src/tabContextMenu.ts:80 #: tabby-core/src/tabContextMenu.ts:80
msgid "Left" msgid "Left"
msgstr "A sinistra" msgstr "Sinistra"
#: locale/tmp-html/tabby-terminal/src/components/terminalSettingsTab.component.html:40 #: locale/tmp-html/tabby-terminal/src/components/terminalSettingsTab.component.html:40
msgid "Lets the shell handle Meta key instead of OS" 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 #: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:78
#: tabby-core/src/tabContextMenu.ts:78 #: tabby-core/src/tabContextMenu.ts:78
msgid "Right" msgid "Right"
msgstr "A destra" msgstr "Destra"
#: locale/tmp-html/tabby-terminal/src/components/terminalSettingsTab.component.html:56 #: locale/tmp-html/tabby-terminal/src/components/terminalSettingsTab.component.html:56
msgid "Right click" msgid "Right click"
@@ -2360,7 +2361,7 @@ msgstr "Sgancia"
#: tabby-core/src/tabContextMenu.ts:81 #: tabby-core/src/tabContextMenu.ts:81
msgid "Up" msgid "Up"
msgstr "In alto" msgstr "Su"
#: locale/tmp-html/tabby-settings/src/components/settingsTab.component.html:14 #: locale/tmp-html/tabby-settings/src/components/settingsTab.component.html:14
#: tabby-electron/src/services/updater.service.ts:133 #: 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 #: tabby-core/src/services/fileProviders.service.ts:40
msgid "Vault master passphrase needs to be set to allow storing secrets" 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 #: locale/tmp-html/tabby-ssh/src/components/sshSettingsTab.component.html:11
msgid "Verify host keys when connecting" 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 #: locale/tmp-html/tabby-settings/src/components/settingsTab.component.html:67
msgid "We're only tracking your Tabby and OS versions." 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 #: tabby-core/src/components/welcomeTab.component.ts:25
msgid "Welcome" msgid "Welcome"
@@ -2509,7 +2510,7 @@ msgstr "Dimensione finestra fuori dal bordo"
#: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:52 #: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:52
msgid "Window frame" msgid "Window frame"
msgstr "Cornice della finestra" msgstr "Cornice finestra"
#: locale/tmp-html/tabby-terminal/src/components/terminalSettingsTab.component.html:165 #: locale/tmp-html/tabby-terminal/src/components/terminalSettingsTab.component.html:165
msgid "Windows" msgid "Windows"
@@ -2525,7 +2526,7 @@ msgstr "Percorso WinSCP"
#: locale/tmp-html/tabby-terminal/src/components/terminalSettingsTab.component.html:74 #: locale/tmp-html/tabby-terminal/src/components/terminalSettingsTab.component.html:74
msgid "Word separators" msgid "Word separators"
msgstr "Separatori di parole" msgstr "Separatori parole"
#: locale/tmp-html/tabby-local/src/components/localProfileSettings.component.html:10 #: locale/tmp-html/tabby-local/src/components/localProfileSettings.component.html:10
msgid "Working directory" 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 #: tabby-terminal/src/hotkeys.ts:54
msgid "Zoom in" msgid "Zoom in"
msgstr "Zoom avanti" msgstr "Zoom +"
#: tabby-terminal/src/hotkeys.ts:58 #: tabby-terminal/src/hotkeys.ts:58
msgid "Zoom out" 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:43
#: locale/tmp-html/tabby-ssh/src/components/sshPortForwardingConfig.component.html:5 #: locale/tmp-html/tabby-ssh/src/components/sshPortForwardingConfig.component.html:5

View File

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

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n" "Project-Id-Version: tabby\n"
"Language-Team: Korean\n" "Language-Team: Korean\n"
"Language: ko_KR\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 #: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?" msgid "\"{command}\" is still running. Close?"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n" "Project-Id-Version: tabby\n"
"Language-Team: Polish\n" "Language-Team: Polish\n"
"Language: pl_PL\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 #: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?" msgid "\"{command}\" is still running. Close?"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n" "Project-Id-Version: tabby\n"
"Language-Team: Portuguese, Brazilian\n" "Language-Team: Portuguese, Brazilian\n"
"Language: pt_BR\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 #: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?" msgid "\"{command}\" is still running. Close?"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n" "Project-Id-Version: tabby\n"
"Language-Team: Portuguese\n" "Language-Team: Portuguese\n"
"Language: pt_PT\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 #: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?" 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 #: locale/tmp-html/tabby-settings/src/components/settingsTab.component.html:84
msgid "Accessibility" msgid "Accessibility"
msgstr "acessibilidade" msgstr ""
#: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:27 #: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:27
msgid "Acrylic background" 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-core/src/components/welcomeTab.component.html:25
#: locale/tmp-html/tabby-terminal/src/components/colorSchemeSettingsTab.component.html:11 #: locale/tmp-html/tabby-terminal/src/components/colorSchemeSettingsTab.component.html:11
msgid "Always dark" msgid "Always dark"
msgstr "sempre escuro" msgstr ""
#: locale/tmp-html/tabby-core/src/components/welcomeTab.component.html:27 #: locale/tmp-html/tabby-core/src/components/welcomeTab.component.html:27
#: locale/tmp-html/tabby-terminal/src/components/colorSchemeSettingsTab.component.html:13 #: locale/tmp-html/tabby-terminal/src/components/colorSchemeSettingsTab.component.html:13
msgid "Always light" msgid "Always light"
msgstr "Sempre luminoso" msgstr ""
#: locale/tmp-html/tabby-terminal/src/components/appearanceSettingsTab.component.html:2 #: locale/tmp-html/tabby-terminal/src/components/appearanceSettingsTab.component.html:2
#: tabby-terminal/src/settings.ts:14 #: 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 #: locale/tmp-html/tabby-terminal/src/components/colorSchemeSettingsForMode.component.html:46
msgid "Custom" msgid "Custom"
msgstr "Personalizado" msgstr ""
#: locale/tmp-html/tabby-terminal/src/components/appearanceSettingsTab.component.html:90 #: locale/tmp-html/tabby-terminal/src/components/appearanceSettingsTab.component.html:90
msgid "Custom CSS" 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 #: locale/tmp-html/tabby-settings/src/components/editProfileGroupModal.component.html:16
msgid "Default profile group settings" 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 #: locale/tmp-html/tabby-settings/src/components/profilesSettingsTab.component.html:101
msgid "Default profile settings" msgid "Default profile settings"
@@ -736,7 +736,7 @@ msgstr "Editar"
#: tabby-electron/src/sftpContextMenu.ts:35 #: tabby-electron/src/sftpContextMenu.ts:35
msgid "Edit locally" msgid "Edit locally"
msgstr "Editar localmente" msgstr ""
#: locale/tmp-html/tabby-plugin-manager/src/components/pluginsSettingsTab.component.html:58 #: locale/tmp-html/tabby-plugin-manager/src/components/pluginsSettingsTab.component.html:58
msgid "Enable" msgid "Enable"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n" "Project-Id-Version: tabby\n"
"Language-Team: Russian\n" "Language-Team: Russian\n"
"Language: ru_RU\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 #: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?" msgid "\"{command}\" is still running. Close?"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n" "Project-Id-Version: tabby\n"
"Language-Team: Serbian (Cyrillic)\n" "Language-Team: Serbian (Cyrillic)\n"
"Language: sr_SP\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 #: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?" msgid "\"{command}\" is still running. Close?"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n" "Project-Id-Version: tabby\n"
"Language-Team: Swedish\n" "Language-Team: Swedish\n"
"Language: sv_SE\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 #: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?" 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 #: locale/tmp-html/tabby-ssh/src/components/hostKeyPromptModal.component.html:25
msgid "Accept just this once" 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 #: locale/tmp-html/tabby-settings/src/components/settingsTab.component.html:84
msgid "Accessibility" msgid "Accessibility"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n" "Project-Id-Version: tabby\n"
"Language-Team: Turkish\n" "Language-Team: Turkish\n"
"Language: tr_TR\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 #: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?" msgid "\"{command}\" is still running. Close?"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n" "Project-Id-Version: tabby\n"
"Language-Team: Ukrainian\n" "Language-Team: Ukrainian\n"
"Language: uk_UA\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 #: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?" msgid "\"{command}\" is still running. Close?"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n" "Project-Id-Version: tabby\n"
"Language-Team: Chinese Simplified\n" "Language-Team: Chinese Simplified\n"
"Language: zh_CN\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 #: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?" msgid "\"{command}\" is still running. Close?"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n" "Project-Id-Version: tabby\n"
"Language-Team: Chinese Traditional\n" "Language-Team: Chinese Traditional\n"
"Language: zh_TW\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 #: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?" msgid "\"{command}\" is still running. Close?"

View File

@@ -39,7 +39,7 @@
"cross-env": "7.0.3", "cross-env": "7.0.3",
"css-loader": "^6.7.3", "css-loader": "^6.7.3",
"deep-equal": "2.0.5", "deep-equal": "2.0.5",
"electron": "^29", "electron": "^27.0.4",
"electron-builder": "^24.6.4", "electron-builder": "^24.6.4",
"electron-download": "^4.1.1", "electron-download": "^4.1.1",
"electron-installer-snap": "^5.1.0", "electron-installer-snap": "^5.1.0",
@@ -53,9 +53,9 @@
"html-loader": "4.2.0", "html-loader": "4.2.0",
"json-loader": "^0.5.7", "json-loader": "^0.5.7",
"lru-cache": "^6.0.0", "lru-cache": "^6.0.0",
"macos-release": "^3.3.0", "macos-release": "^3.1.0",
"ngx-toastr": "^16.0.2", "ngx-toastr": "^16.0.2",
"node-abi": "^3.65.0", "node-abi": "^3.51.0",
"npmlog": "6.0.2", "npmlog": "6.0.2",
"npx": "^10.2.2", "npx": "^10.2.2",
"patch-package": "^6.4.7", "patch-package": "^6.4.7",
@@ -76,6 +76,7 @@
"source-code-pro": "^2.38.0", "source-code-pro": "^2.38.0",
"source-map-loader": "^4.0.1", "source-map-loader": "^4.0.1",
"source-sans-pro": "3.6.0", "source-sans-pro": "3.6.0",
"ssh2": "^1.14.0",
"style-loader": "^3.3.1", "style-loader": "^3.3.1",
"svg-inline-loader": "^0.8.2", "svg-inline-loader": "^0.8.2",
"thenby": "^1.3.4", "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: { config: {
extraMetadata: { extraMetadata: {
version: vars.version, version: vars.version,
teamId: process.env.APPLE_TEAM_ID,
}, },
mac: { mac: {
identity: !process.env.CI || process.env.CSC_LINK ? undefined : null, 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 semver from 'semver'
import * as childProcess from 'child_process' 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' import * as url from 'url'
const __dirname = url.fileURLToPath(new URL('.', import.meta.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 { TabContextMenuItemProvider } from './tabContextMenuProvider'
export { SelectorOption } from './selector' export { SelectorOption } from './selector'
export { CLIHandler, CLIEvent } from './cli' 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 { MenuItemOptions } from './menu'
export { BootstrapData, PluginInfo, BOOTSTRAP_DATA } from './mainProcess' export { BootstrapData, PluginInfo, BOOTSTRAP_DATA } from './mainProcess'
export { HostWindowService } from './hostWindow' export { HostWindowService } from './hostWindow'

View File

@@ -63,24 +63,22 @@ export abstract class FileTransfer {
} }
export abstract class FileDownload extends 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 { export abstract class FileUpload extends FileTransfer {
abstract read (): Promise<Uint8Array> abstract read (): Promise<Buffer>
async readAll (): Promise<Uint8Array> { async readAll (): Promise<Buffer> {
const result = new Uint8Array(this.getSize()) const buffers: Buffer[] = []
let pos = 0
while (true) { while (true) {
const buf = await this.read() const buf = await this.read()
if (!buf.length) { if (!buf.length) {
break break
} }
result.set(buf, pos) buffers.push(Buffer.from(buf))
pos += buf.length
} }
return result return Buffer.concat(buffers)
} }
} }
@@ -88,26 +86,6 @@ export interface FileUploadOptions {
multiple: boolean 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 type PlatformTheme = 'light'|'dark'
export abstract class PlatformService { export abstract class PlatformService {
@@ -128,54 +106,23 @@ export abstract class PlatformService {
abstract startDownload (name: string, mode: number, size: number): Promise<FileDownload|null> abstract startDownload (name: string, mode: number, size: number): Promise<FileDownload|null>
abstract startUpload (options?: FileUploadOptions): Promise<FileUpload[]> 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) { 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 // eslint-disable-next-line @typescript-eslint/prefer-for-of
for (let i = 0; i < items.length; i++) { for (let i = 0; i < event.dataTransfer.files.length; i++) {
const item = items[i].webkitGetAsEntry() const file = event.dataTransfer.files[i]
if (item) { const transfer = new HTMLFileUpload(file)
promises.push(traverseFileTree(item)) this.fileTransferStarted.next(transfer)
if (!multiple) { result.push(transfer)
break if (!multiple) {
} break
} }
} }
return Promise.all(promises).then(() => result) return result
} }
getConfigPath (): string|null { getConfigPath (): string|null {
@@ -263,12 +210,12 @@ export class HTMLFileUpload extends FileUpload {
return this.file.size return this.file.size
} }
async read (): Promise<Uint8Array> { async read (): Promise<Buffer> {
const result: any = await this.reader.read() const result: any = await this.reader.read()
if (result.done || !result.value) { 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) this.increaseProgress(chunk.length)
return chunk return chunk
} }

View File

@@ -1,7 +1,11 @@
.icon(
[fastHtmlBind]='pngPath',
*ngIf='!isHTML && isPNG'
)
i.icon( i.icon(
class='fa-fw {{icon}}', class='fa-fw {{icon}}',
[style.color]='color', [style.color]='color',
*ngIf='!isHTML' *ngIf='!isHTML && !isPNG'
) )
.icon( .icon(
[fastHtmlBind]='icon', [fastHtmlBind]='icon',

View File

@@ -12,7 +12,15 @@ export class ProfileIconComponent extends BaseComponent {
@Input() icon?: string @Input() icon?: string
@Input() color?: string @Input() color?: string
get pngPath (): string {
return `<img src="${this.icon?.trim()}" width="16" height="16" />`
}
get isHTML (): boolean { get isHTML (): boolean {
return this.icon?.startsWith('<') ?? false return this.icon?.startsWith('<') ?? false
} }
get isPNG (): boolean {
return this.icon?.endsWith('.png') ?? false
}
} }

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

View File

@@ -1,5 +1,5 @@
import { Directive, Output, ElementRef, EventEmitter, AfterViewInit } from '@angular/core' 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' import './dropZone.directive.scss'
/** @hidden */ /** @hidden */
@@ -7,7 +7,7 @@ import './dropZone.directive.scss'
selector: '[dropZone]', selector: '[dropZone]',
}) })
export class DropZoneDirective implements AfterViewInit { export class DropZoneDirective implements AfterViewInit {
@Output() transfer = new EventEmitter<DirectoryUpload>() @Output() transfer = new EventEmitter<FileUpload>()
private dropHint?: HTMLElement private dropHint?: HTMLElement
constructor ( 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.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.el.nativeElement.addEventListener('dragleave', () => {
this.removeHint() this.removeHint()

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ import * as os from 'os'
import promiseIpc, { RendererProcessType } from 'electron-promise-ipc' import promiseIpc, { RendererProcessType } from 'electron-promise-ipc'
import { execFile } from 'mz/child_process' import { execFile } from 'mz/child_process'
import { Injectable, NgZone } from '@angular/core' 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 { ElectronService } from '../services/electron.service'
import { ElectronHostWindow } from './hostWindow.service' import { ElectronHostWindow } from './hostWindow.service'
import { ShellIntegrationService } from './shellIntegration.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 { readClipboard (): string {
return this.electron.clipboard.readText() 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> { async startDownload (name: string, mode: number, size: number, filePath?: string): Promise<FileDownload|null> {
if (!filePath) { if (!filePath) {
const result = await this.electron.dialog.showSaveDialog( const result = await this.electron.dialog.showSaveDialog(
@@ -300,12 +263,12 @@ class ElectronFileUpload extends FileUpload {
private size: number private size: number
private mode: number private mode: number
private file: fs.FileHandle private file: fs.FileHandle
private buffer: Uint8Array private buffer: Buffer
private powerSaveBlocker = 0 private powerSaveBlocker = 0
constructor (private filePath: string, private electron: ElectronService) { constructor (private filePath: string, private electron: ElectronService) {
super() super()
this.buffer = new Uint8Array(256 * 1024) this.buffer = Buffer.alloc(256 * 1024)
this.powerSaveBlocker = electron.powerSaveBlocker.start('prevent-app-suspension') this.powerSaveBlocker = electron.powerSaveBlocker.start('prevent-app-suspension')
} }
@@ -328,7 +291,7 @@ class ElectronFileUpload extends FileUpload {
return this.size return this.size
} }
async read (): Promise<Uint8Array> { async read (): Promise<Buffer> {
const result = await this.file.read(this.buffer, 0, this.buffer.length, null) const result = await this.file.read(this.buffer, 0, this.buffer.length, null)
this.increaseProgress(result.bytesRead) this.increaseProgress(result.bytesRead)
return this.buffer.slice(0, result.bytesRead) return this.buffer.slice(0, result.bytesRead)
@@ -370,7 +333,7 @@ class ElectronFileDownload extends FileDownload {
return this.size return this.size
} }
async write (buffer: Uint8Array): Promise<void> { async write (buffer: Buffer): Promise<void> {
let pos = 0 let pos = 0
while (pos < buffer.length) { while (pos < buffer.length) {
const result = await this.file.write(buffer, pos, buffer.length - pos, null) 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 { Injectable } from '@angular/core'
import axios from 'axios' import axios from 'axios'
@@ -12,6 +13,7 @@ export class ElectronUpdaterService extends UpdaterService {
private downloaded: Promise<boolean> private downloaded: Promise<boolean>
private electronUpdaterAvailable = true private electronUpdaterAvailable = true
private updateURL: string private updateURL: string
private autoUpdater: AppUpdater
constructor ( constructor (
log: LogService, log: LogService,
@@ -28,28 +30,32 @@ export class ElectronUpdaterService extends UpdaterService {
return 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.logger.info('Update available')
}) })
this.electron.ipcRenderer.on('updater:update-not-available', () => { this.autoUpdater.on('update-not-available', () => {
this.logger.info('No updates') this.logger.info('No updates')
}) })
this.electron.ipcRenderer.on('updater:error', err => { this.autoUpdater.on('error', err => {
this.logger.error(err) this.logger.error(err)
this.electronUpdaterAvailable = false this.electronUpdaterAvailable = false
}) })
this.downloaded = new Promise<boolean>(resolve => { 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(() => { config.ready$.toPromise().then(() => {
if (config.store.enableAutomaticUpdates && this.electronUpdaterAvailable && !process.env.TABBY_DEV) { if (config.store.enableAutomaticUpdates && this.electronUpdaterAvailable && !process.env.TABBY_DEV) {
this.logger.debug('Checking for updates') this.logger.debug('Checking for updates')
try { try {
this.electron.ipcRenderer.send('updater:check-for-updates') this.autoUpdater.checkForUpdates()
} catch (e) { } catch (e) {
this.electronUpdaterAvailable = false this.electronUpdaterAvailable = false
this.logger.info('Electron updater unavailable, falling back', e) this.logger.info('Electron updater unavailable, falling back', e)
@@ -76,26 +82,26 @@ export class ElectronUpdaterService extends UpdaterService {
reject(err) reject(err)
} }
cancel = () => { cancel = () => {
this.electron.ipcRenderer.off('updater:error', onError) this.autoUpdater.off('error', onError)
this.electron.ipcRenderer.off('updater:update-not-available', onNoUpdate) this.autoUpdater.off('update-not-available', onNoUpdate)
this.electron.ipcRenderer.off('updater:update-available', onUpdate) this.autoUpdater.off('update-available', onUpdate)
} }
this.electron.ipcRenderer.on('updater:error', onError) this.autoUpdater.on('error', onError)
this.electron.ipcRenderer.on('updater:update-not-available', onNoUpdate) this.autoUpdater.on('update-not-available', onNoUpdate)
this.electron.ipcRenderer.on('updater:update-available', onUpdate) this.autoUpdater.on('update-available', onUpdate)
try { try {
this.electron.ipcRenderer.send('updater:check-for-updates') this.autoUpdater.checkForUpdates()
} catch (e) { } catch (e) {
this.electronUpdaterAvailable = false this.electronUpdaterAvailable = false
this.logger.info('Electron updater unavailable, falling back', e) 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.logger.info('Update available')
}) })
this.electron.ipcRenderer.once('updater:update-not-available', () => { this.autoUpdater.once('update-not-available', () => {
this.logger.info('No updates') this.logger.info('No updates')
}) })
@@ -132,7 +138,7 @@ export class ElectronUpdaterService extends UpdaterService {
}, },
)).response === 0) { )).response === 0) {
await this.downloaded 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) this.platform.openPath(tempPath)
const events = new Subject<string>() const events = new Subject<string>()
fs.chmodSync(tempPath, 0o700) const watcher = fs.watch(tempPath, event => events.next(event))
events.pipe(debounceTime(1000), debounce(async event => {
// skip the first burst of events if (event === 'rename') {
setTimeout(() => { watcher.close()
const watcher = fs.watch(tempPath, event => events.next(event)) }
events.pipe(debounceTime(1000), debounce(async event => { const upload = await this.platform.startUpload({ multiple: false }, [tempPath])
if (event === 'rename') { if (!upload.length) {
watcher.close() return
} }
const upload = await this.platform.startUpload({ multiple: false }, [tempPath]) await sftp.upload(item.fullPath, upload[0])
if (!upload.length) { await sftp.chmod(item.fullPath, item.mode)
return })).subscribe()
} watcher.on('close', () => events.complete())
await sftp.upload(item.fullPath, upload[0]) sftp.closed$.subscribe(() => watcher.close())
await sftp.chmod(item.fullPath, item.mode)
})).subscribe()
watcher.on('close', () => events.complete())
sftp.closed$.subscribe(() => watcher.close())
}, 1000)
} }
} }

View File

@@ -6,6 +6,8 @@ import { HostAppService, Platform, isWindowsBuild, WIN_BUILD_WSL_EXE_DISTRO_FLAG
import { ShellProvider, Shell } from 'tabby-local' import { ShellProvider, Shell } from 'tabby-local'
import { PowerShell } from 'node-powershell'
/* eslint-disable block-scoped-var */ /* eslint-disable block-scoped-var */
try { try {
@@ -38,10 +40,42 @@ const wslIconMap: Record<string, string> = {
/** @hidden */ /** @hidden */
@Injectable() @Injectable()
export class WSLShellProvider extends ShellProvider { export class WSLShellProvider extends ShellProvider {
private _pwsh: PowerShell
constructor ( constructor (
private hostApp: HostAppService, private hostApp: HostAppService,
) { ) {
super() super()
// make sure that this will not use the powershell profile
// that may take a long time to load
this._pwsh = new PowerShell({
executableOptions: {
'-NoProfile': true,
},
})
}
private async _resolveIcon (defaultDistKey: any): Promise<string> {
let _icon = wslIconMap.Linux
// check if the register has PackageFamilyName
if (defaultDistKey.PackageFamilyName) {
// get the icon from the package family name
const packageFamilyName = (defaultDistKey.PackageFamilyName.value as string).split('_')[0]
if (packageFamilyName) {
const _ret = await this._pwsh.invoke(`Get-AppxPackage ${packageFamilyName} | ConvertTo-Json`)
if (!_ret.hadErrors && _ret.stdout?.toString() !== undefined && _ret.stdout.toString() !== '') {
const appx = JSON.parse(_ret.stdout.toString())
const installationLocation = appx.InstallLocation
_icon = `${installationLocation}\\Assets\\Square44x44Logo.targetsize-16.png`
}
}
}
return _icon
} }
async provide (): Promise<Shell[]> { async provide (): Promise<Shell[]> {
@@ -59,6 +93,7 @@ export class WSLShellProvider extends ShellProvider {
if (lxss?.DefaultDistribution) { if (lxss?.DefaultDistribution) {
const defaultDistKey = wnr.getRegistryKey(wnr.HK.CU, lxssPath + '\\' + String(lxss.DefaultDistribution.value)) const defaultDistKey = wnr.getRegistryKey(wnr.HK.CU, lxssPath + '\\' + String(lxss.DefaultDistribution.value))
if (defaultDistKey?.DistributionName) { if (defaultDistKey?.DistributionName) {
const _icon = await this._resolveIcon(defaultDistKey)
const shell: Shell = { const shell: Shell = {
id: 'wsl', id: 'wsl',
name: 'WSL / Default distro', name: 'WSL / Default distro',
@@ -68,7 +103,7 @@ export class WSLShellProvider extends ShellProvider {
COLORTERM: 'truecolor', COLORTERM: 'truecolor',
}, },
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
icon: wslIconMap[defaultDistKey.DistributionName.value] ?? wslIconMap.Linux, icon: wslIconMap[defaultDistKey.DistributionName.value] ?? _icon,
} }
shells.push(shell) shells.push(shell)
} }
@@ -90,11 +125,14 @@ export class WSLShellProvider extends ShellProvider {
return [] return []
} }
} }
for (const child of wnr.listRegistrySubkeys(wnr.HK.CU, lxssPath) as string[]) { for (const child of wnr.listRegistrySubkeys(wnr.HK.CU, lxssPath) as string[]) {
const childKey = wnr.getRegistryKey(wnr.HK.CU, lxssPath + '\\' + child) const childKey = wnr.getRegistryKey(wnr.HK.CU, lxssPath + '\\' + child)
if (!childKey.DistributionName || !childKey.BasePath) { if (!childKey.DistributionName || !childKey.BasePath) {
continue continue
} }
const _icon = await this._resolveIcon(childKey)
const wslVersion = (childKey.Flags?.value || 0) & 8 ? 2 : 1 const wslVersion = (childKey.Flags?.value || 0) & 8 ? 2 : 1
const name = childKey.DistributionName.value const name = childKey.DistributionName.value
const fsBase = wslVersion === 2 ? `\\\\wsl$\\${name}` : childKey.BasePath.value as string + '\\rootfs' const fsBase = wslVersion === 2 ? `\\\\wsl$\\${name}` : childKey.BasePath.value as string + '\\rootfs'
@@ -110,7 +148,7 @@ export class WSLShellProvider extends ShellProvider {
COLORTERM: 'truecolor', COLORTERM: 'truecolor',
}, },
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
icon: wslIconMap[name] ?? wslIconMap.Linux, icon: wslIconMap[name] ?? _icon,
} }
shells.push(shell) shells.push(shell)
} }

View File

@@ -11,3 +11,8 @@ untildify@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b"
integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== 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 div(translate) Report a problem
small.text-muted(translate) Generate a pre-filled GitHub issue small.text-muted(translate) Generate a pre-filled GitHub issue
button.list-group-item.list-group-item-action.link-card((click)='homeBase.openDiscord()') button.list-group-item.list-group-item-action.link-card((click)='homeBase.openDiscussions()')
i.fab.fa-fw.fa-discord i.fas.fa-fw.fa-comments
div div
div(translate) Community div(translate) Ask a question
small.text-muted(translate) On Discord small.text-muted(translate) On GitHub Discussions
button.list-group-item.list-group-item-action.link-card((click)='homeBase.openGitHub()') button.list-group-item.list-group-item-action.link-card((click)='homeBase.openGitHub()')
i.fab.fa-fw.fa-github i.fab.fa-fw.fa-github
@@ -58,6 +58,12 @@
div(translate) What's new div(translate) What's new
small.text-muted(translate) Show release notes 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 h3(translate) Application settings
.form-line .form-line

View File

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

View File

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

View File

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

View File

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

View File

@@ -51,3 +51,13 @@ export interface ForwardedPortConfig {
targetPort: number targetPort: number
description: string 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 .d-flex.mt-3
checkbox( button.btn.btn-secondary(
*ngIf='isPassword()',
[(ngModel)]='remember',
[text]='"Save password"|translate'
)
.ms-auto
button.btn.btn-secondary.me-3(
*ngIf='step > 0', *ngIf='step > 0',
(click)='previous()' (click)='previous()'
) )
.ms-auto
button.btn.btn-primary( button.btn.btn-primary(
(click)='next()' (click)='next()'
) )

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
import * as C from 'constants' import * as C from 'constants'
import { posix as path } from 'path' import { posix as path } from 'path'
import { Component, Input, Output, EventEmitter, Inject, Optional } from '@angular/core' 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 { SFTPSession, SFTPFile } from '../session/sftp'
import { SSHSession } from '../session/ssh' import { SSHSession } from '../session/ssh'
import { SFTPContextMenuItemProvider } from '../api' import { SFTPContextMenuItemProvider } from '../api'
@@ -180,30 +180,6 @@ export class SFTPPanelComponent {
await Promise.all(transfers.map(t => this.uploadOne(t))) 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> { async uploadOne (transfer: FileUpload): Promise<void> {
const savedPath = this.path const savedPath = this.path
await this.sftp.upload(path.join(this.path, transfer.getName()), transfer) await this.sftp.upload(path.join(this.path, transfer.getName()), transfer)

View File

@@ -51,7 +51,6 @@ sftp-panel.bg-dark(
keyboard-interactive-auth-panel.bg-dark( keyboard-interactive-auth-panel.bg-dark(
*ngIf='activeKIPrompt', *ngIf='activeKIPrompt',
[prompt]='activeKIPrompt', [prompt]='activeKIPrompt',
[profile]='profile',
(click)='$event.stopPropagation()', (click)='$event.stopPropagation()',
(done)='activeKIPrompt = null; frontend?.focus()' (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 { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'
import colors from 'ansi-colors' import colors from 'ansi-colors'
import { Component, Injector, HostListener } from '@angular/core' import { Component, Injector, HostListener } from '@angular/core'
@@ -95,21 +94,17 @@ export class SSHTabComponent extends ConnectableTerminalTabComponent<SSHProfile>
} }
}) })
if (!(jumpSession.ssh instanceof russh.AuthenticatedSSHClient)) { session.jumpStream = await new Promise((resolve, reject) => jumpSession.ssh.forwardOut(
throw new Error('Jump session is not authenticated yet somehow') '127.0.0.1', 0, profile.options.host, profile.options.port ?? 22,
} (err, stream) => {
if (err) {
try { jumpSession.emitServiceMessage(colors.bgRed.black(' X ') + ` Could not set up port forward on ${jumpConnection.name}`)
session.jumpChannel = await jumpSession.ssh.openTCPForwardChannel({ reject(err)
addressToConnectTo: profile.options.host, return
portToConnectTo: profile.options.port ?? 22, }
originatorAddress: '127.0.0.1', resolve(stream)
originatorPort: 0, },
}) ))
} catch (err) {
jumpSession.emitServiceMessage(colors.bgRed.black(' X ') + ` Could not set up port forward on ${jumpConnection.name}`)
throw err
}
} }
} }
@@ -130,7 +125,7 @@ export class SSHTabComponent extends ConnectableTerminalTabComponent<SSHProfile>
}) })
if (!session.open) { 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'))) this.startSpinner(this.translate.instant(_('Connecting')))

View File

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

View File

@@ -0,0 +1,12 @@
import 'ssh2'
const nodeCrypto = require('crypto')
const browserDH = require('diffie-hellman/browser')
nodeCrypto.createDiffieHellmanGroup = browserDH.createDiffieHellmanGroup
nodeCrypto.createDiffieHellman = browserDH.createDiffieHellman
// Declare function missing from @types
declare module 'ssh2' {
interface Client {
setNoDelay: (enable?: boolean) => this
}
}

View File

@@ -1,11 +1,11 @@
import { Injectable, InjectFlags, Injector } from '@angular/core' import { Injectable, InjectFlags, Injector } from '@angular/core'
import { NewTabParameters, PartialProfile, TranslateService, QuickConnectProfileProvider } from 'tabby-core' import { NewTabParameters, PartialProfile, TranslateService, QuickConnectProfileProvider } from 'tabby-core'
import * as ALGORITHMS from 'ssh2/lib/protocol/constants'
import { SSHProfileSettingsComponent } from './components/sshProfileSettings.component' import { SSHProfileSettingsComponent } from './components/sshProfileSettings.component'
import { SSHTabComponent } from './components/sshTab.component' import { SSHTabComponent } from './components/sshTab.component'
import { PasswordStorageService } from './services/passwordStorage.service' import { PasswordStorageService } from './services/passwordStorage.service'
import { SSHAlgorithmType, SSHProfile } from './api' import { ALGORITHM_BLACKLIST, SSHAlgorithmType, SSHProfile } from './api'
import { SSHProfileImporter } from './api/importer' import { SSHProfileImporter } from './api/importer'
import { defaultAlgorithms } from './algorithms'
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class SSHProfilesService extends QuickConnectProfileProvider<SSHProfile> { export class SSHProfilesService extends QuickConnectProfileProvider<SSHProfile> {
@@ -29,10 +29,10 @@ export class SSHProfilesService extends QuickConnectProfileProvider<SSHProfile>
agentForward: false, agentForward: false,
warnOnClose: null, warnOnClose: null,
algorithms: { algorithms: {
hmac: [] as string[], hmac: [],
kex: [] as string[], kex: [],
cipher: [] as string[], cipher: [],
serverHostKey: [] as string[], serverHostKey: [],
}, },
proxyCommand: null, proxyCommand: null,
forwardedPorts: [], forwardedPorts: [],
@@ -54,7 +54,13 @@ export class SSHProfilesService extends QuickConnectProfileProvider<SSHProfile>
) { ) {
super() super()
for (const k of Object.values(SSHAlgorithmType)) { 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() 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 * as tmp from 'tmp-promise'
import socksv5 from '@luminati-io/socksv5'
import { Duplex } from 'stream'
import { Injectable } from '@angular/core' 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 { ConfigService, HostAppService, Platform, PlatformService } from 'tabby-core'
import { SSHSession } from '../session/ssh' import { SSHSession } from '../session/ssh'
import { SSHProfile } from '../api' import { SSHProfile, SSHProxyStream, SSHProxyStreamSocket } from '../api'
import { PasswordStorageService } from './passwordStorage.service' import { PasswordStorageService } from './passwordStorage.service'
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
@@ -49,7 +55,7 @@ export class SSHService {
let tmpFile: tmp.FileResult|null = null let tmpFile: tmp.FileResult|null = null
if (session.activePrivateKey) { if (session.activePrivateKey) {
tmpFile = await tmp.file() 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' const winSCPcom = path.slice(0, -3) + 'com'
await this.platform.exec(winSCPcom, ['/keygen', tmpFile.path, `/output=${tmpFile.path}`]) await this.platform.exec(winSCPcom, ['/keygen', tmpFile.path, `/output=${tmpFile.path}`])
args.push(`/privatekey=${tmpFile.path}`) args.push(`/privatekey=${tmpFile.path}`)
@@ -58,3 +64,171 @@ export class SSHService {
tmpFile?.cleanup() 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 { Subject, Observable } from 'rxjs'
import { posix as posixPath } from 'path' import { posix as posixPath } from 'path'
import { Injector } from '@angular/core' import { Injector, NgZone } from '@angular/core'
import { FileDownload, FileUpload, Logger, LogService } from 'tabby-core' import { FileDownload, FileUpload, Logger, LogService, wrapPromise } from 'tabby-core'
import * as russh from 'russh' import { SFTPWrapper } from 'ssh2'
import { promisify } from 'util'
import type { FileEntry, Stats } from 'ssh2-streams'
export interface SFTPFile { export interface SFTPFile {
name: string name: string
@@ -19,37 +22,63 @@ export class SFTPFileHandle {
position = 0 position = 0
constructor ( constructor (
private inner: russh.SFTPFile|null, private sftp: SFTPWrapper,
private handle: Buffer,
private zone: NgZone,
) { } ) { }
async read (): Promise<Uint8Array> { read (): Promise<Buffer> {
if (!this.inner) { const buffer = Buffer.alloc(256 * 1024)
return Promise.resolve(new Uint8Array(0)) return wrapPromise(this.zone, new Promise((resolve, reject) => {
} while (true) {
return this.inner.read(256 * 1024) 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> { write (chunk: Buffer): Promise<void> {
if (!this.inner) { return wrapPromise(this.zone, new Promise<void>((resolve, reject) => {
throw new Error('File handle is closed') while (true) {
} const wait = this.sftp.write(this.handle, chunk, 0, chunk.length, this.position, err => {
await this.inner.writeAll(chunk) if (err) {
reject(err)
return
}
this.position += chunk.length
resolve()
})
if (!wait) {
break
}
}
}))
} }
async close (): Promise<void> { close (): Promise<void> {
await this.inner?.shutdown() return wrapPromise(this.zone, promisify(this.sftp.close.bind(this.sftp))(this.handle))
this.inner = null
} }
} }
export class SFTPSession { export class SFTPSession {
get closed$ (): Observable<void> { return this.closed } get closed$ (): Observable<void> { return this.closed }
private closed = new Subject<void>() private closed = new Subject<void>()
private zone: NgZone
private logger: Logger 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') this.logger = injector.get(LogService).create('sftp')
sftp.closed$.subscribe(() => { sftp.on('close', () => {
this.closed.next() this.closed.next()
this.closed.complete() this.closed.complete()
}) })
@@ -57,64 +86,67 @@ export class SFTPSession {
async readdir (p: string): Promise<SFTPFile[]> { async readdir (p: string): Promise<SFTPFile[]> {
this.logger.debug('readdir', p) 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( return entries.map(entry => this._makeFile(
posixPath.join(p, entry.name), entry, posixPath.join(p, entry.filename), entry,
)) ))
} }
readlink (p: string): Promise<string> { readlink (p: string): Promise<string> {
this.logger.debug('readlink', p) 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> { async stat (p: string): Promise<SFTPFile> {
this.logger.debug('stat', p) 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 { return {
name: posixPath.basename(p), name: posixPath.basename(p),
fullPath: p, fullPath: p,
isDirectory: stats.type === russh.SFTPFileType.Directory, isDirectory: stats.isDirectory(),
isSymlink: stats.type === russh.SFTPFileType.Symlink, isSymlink: stats.isSymbolicLink(),
mode: stats.permissions ?? 0, mode: stats.mode,
size: stats.size, size: stats.size,
modified: new Date((stats.mtime ?? 0) * 1000), modified: new Date(stats.mtime * 1000),
} }
} }
async open (p: string, mode: number): Promise<SFTPFileHandle> { async open (p: string, mode: string): Promise<SFTPFileHandle> {
this.logger.debug('open', p, mode) this.logger.debug('open', p)
const handle = await this.sftp.open(p, mode) const handle = await wrapPromise(this.zone, promisify<Buffer>(f => this.sftp.open(p, mode, f))())
return new SFTPFileHandle(handle) return new SFTPFileHandle(this.sftp, handle, this.zone)
} }
async rmdir (p: string): Promise<void> { 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> { 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> { async rename (oldPath: string, newPath: string): Promise<void> {
this.logger.debug('rename', oldPath, newPath) 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> { 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> { async chmod (p: string, mode: string|number): Promise<void> {
this.logger.debug('chmod', p, mode) 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> { async upload (path: string, transfer: FileUpload): Promise<void> {
this.logger.info('Uploading into', path) this.logger.info('Uploading into', path)
const tempPath = path + '.tabby-upload' const tempPath = path + '.tabby-upload'
try { try {
const handle = await this.open(tempPath, russh.OPEN_WRITE | russh.OPEN_CREATE) const handle = await this.open(tempPath, 'w')
while (true) { while (true) {
const chunk = await transfer.read() const chunk = await transfer.read()
if (!chunk.length) { if (!chunk.length) {
@@ -122,13 +154,15 @@ export class SFTPSession {
} }
await handle.write(chunk) await handle.write(chunk)
} }
await handle.close() handle.close()
await this.unlink(path).catch(() => null) try {
await this.unlink(path)
} catch { }
await this.rename(tempPath, path) await this.rename(tempPath, path)
transfer.close() transfer.close()
} catch (e) { } catch (e) {
transfer.cancel() transfer.cancel()
this.unlink(tempPath).catch(() => null) this.unlink(tempPath)
throw e throw e
} }
} }
@@ -136,7 +170,7 @@ export class SFTPSession {
async download (path: string, transfer: FileDownload): Promise<void> { async download (path: string, transfer: FileDownload): Promise<void> {
this.logger.info('Downloading', path) this.logger.info('Downloading', path)
try { try {
const handle = await this.open(path, russh.OPEN_READ) const handle = await this.open(path, 'r')
while (true) { while (true) {
const chunk = await handle.read() const chunk = await handle.read()
if (!chunk.length) { 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 { return {
fullPath: p, fullPath: p,
name: posixPath.basename(p), name: posixPath.basename(p),
isDirectory: entry.metadata.type === russh.SFTPFileType.Directory, isDirectory: (entry.attrs.mode & C.S_IFDIR) === C.S_IFDIR,
isSymlink: entry.metadata.type === russh.SFTPFileType.Symlink, isSymlink: (entry.attrs.mode & C.S_IFLNK) === C.S_IFLNK,
mode: entry.metadata.permissions ?? 0, mode: entry.attrs.mode,
size: entry.metadata.size, size: entry.attrs.size,
modified: new Date((entry.metadata.mtime ?? 0) * 1000), modified: new Date(entry.attrs.mtime * 1000),
} }
} }
} }

View File

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

View File

@@ -1,22 +1,24 @@
import * as fs from 'mz/fs' import * as fs from 'mz/fs'
import * as crypto from 'crypto' import * as crypto from 'crypto'
import * as sshpk from 'sshpk'
import colors from 'ansi-colors' import colors from 'ansi-colors'
import stripAnsi from 'strip-ansi' import stripAnsi from 'strip-ansi'
import * as shellQuote from 'shell-quote' import { Injector, NgZone } from '@angular/core'
import { Injector } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap' 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 { Socket } from 'net'
import { Client, ClientChannel, SFTPWrapper } from 'ssh2'
import { Subject, Observable } from 'rxjs' import { Subject, Observable } from 'rxjs'
import { HostKeyPromptModalComponent } from '../components/hostKeyPromptModal.component' import { HostKeyPromptModalComponent } from '../components/hostKeyPromptModal.component'
import { HTTPProxyStream, ProxyCommandStream, SocksProxyStream } from '../services/ssh.service'
import { PasswordStorageService } from '../services/passwordStorage.service' import { PasswordStorageService } from '../services/passwordStorage.service'
import { SSHKnownHostsService } from '../services/sshKnownHosts.service' import { SSHKnownHostsService } from '../services/sshKnownHosts.service'
import { promisify } from 'util'
import { SFTPSession } from './sftp' import { SFTPSession } from './sftp'
import { SSHAlgorithmType, SSHProfile, AutoPrivateKeyLocator, PortForwardType } from '../api' import { SSHAlgorithmType, PortForwardType, SSHProfile, SSHProxyStream, AutoPrivateKeyLocator } from '../api'
import { ForwardedPort } from './forwards' import { ForwardedPort } from './forwards'
import { X11Socket } from './x11' import { X11Socket } from './x11'
import { supportedAlgorithms } from '../algorithms' import { supportedAlgorithms } from '../algorithms'
import * as russh from 'russh'
const WINDOWS_OPENSSH_AGENT_PIPE = '\\\\.\\pipe\\openssh-ssh-agent' const WINDOWS_OPENSSH_AGENT_PIPE = '\\\\.\\pipe\\openssh-ssh-agent'
@@ -25,74 +27,48 @@ export interface Prompt {
echo?: boolean echo?: boolean
} }
type AuthMethod = { interface AuthMethod {
type: 'none'|'prompt-password'|'hostbased' type: 'none'|'publickey'|'agent'|'password'|'keyboard-interactive'|'hostbased'
} | { name?: string
type: 'keyboard-interactive', contents?: Buffer
savedPassword?: string }
} | {
type: 'saved-password', interface Handshake {
password: string kex: string
} | { serverHostKey: 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',
} }
export class KeyboardInteractivePrompt { export class KeyboardInteractivePrompt {
readonly responses: string[] = [] 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
})
constructor ( constructor (
public name: string, public name: string,
public instruction: string, public instruction: string,
public prompts: Prompt[], public prompts: Prompt[],
private callback: (_: string[]) => void,
) { ) {
this.responses = new Array(this.prompts.length).fill('') 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 { respond (): void {
this._resolve(this.responses) this.callback(this.responses)
}
reject (): void {
this._reject(new Error('Keyboard-interactive auth rejected'))
} }
} }
export class SSHSession { export class SSHSession {
shell?: russh.Channel shell?: ClientChannel
ssh: russh.SSHClient|russh.AuthenticatedSSHClient ssh: Client
sftp?: russh.SFTP sftp?: SFTPWrapper
forwardedPorts: ForwardedPort[] = [] forwardedPorts: ForwardedPort[] = []
jumpChannel: russh.Channel|null = null jumpStream: any
proxyCommandStream: SSHProxyStream|null = null
savedPassword?: string savedPassword?: string
get serviceMessage$ (): Observable<string> { return this.serviceMessage } get serviceMessage$ (): Observable<string> { return this.serviceMessage }
get keyboardInteractivePrompt$ (): Observable<KeyboardInteractivePrompt> { return this.keyboardInteractivePrompt } get keyboardInteractivePrompt$ (): Observable<KeyboardInteractivePrompt> { return this.keyboardInteractivePrompt }
get willDestroy$ (): Observable<void> { return this.willDestroy } get willDestroy$ (): Observable<void> { return this.willDestroy }
activePrivateKey: russh.KeyPair|null = null agentPath?: string
activePrivateKey: string|null = null
authUsername: string|null = null authUsername: string|null = null
open = false open = false
@@ -103,11 +79,15 @@ export class SSHSession {
private serviceMessage = new Subject<string>() private serviceMessage = new Subject<string>()
private keyboardInteractivePrompt = new Subject<KeyboardInteractivePrompt>() private keyboardInteractivePrompt = new Subject<KeyboardInteractivePrompt>()
private willDestroy = new Subject<void>() private willDestroy = new Subject<void>()
private keychainPasswordUsed = false
private hostKeyDigest = ''
private passwordStorage: PasswordStorageService private passwordStorage: PasswordStorageService
private ngbModal: NgbModal private ngbModal: NgbModal
private hostApp: HostAppService private hostApp: HostAppService
private platform: PlatformService
private notifications: NotificationsService private notifications: NotificationsService
private zone: NgZone
private fileProviders: FileProvidersService private fileProviders: FileProvidersService
private config: ConfigService private config: ConfigService
private translate: TranslateService private translate: TranslateService
@@ -123,7 +103,9 @@ export class SSHSession {
this.passwordStorage = injector.get(PasswordStorageService) this.passwordStorage = injector.get(PasswordStorageService)
this.ngbModal = injector.get(NgbModal) this.ngbModal = injector.get(NgbModal)
this.hostApp = injector.get(HostAppService) this.hostApp = injector.get(HostAppService)
this.platform = injector.get(PlatformService)
this.notifications = injector.get(NotificationsService) this.notifications = injector.get(NotificationsService)
this.zone = injector.get(NgZone)
this.fileProviders = injector.get(FileProvidersService) this.fileProviders = injector.get(FileProvidersService)
this.config = injector.get(ConfigService) this.config = injector.get(ConfigService)
this.translate = injector.get(TranslateService) this.translate = injector.get(TranslateService)
@@ -138,6 +120,27 @@ export class SSHSession {
} }
async init (): Promise<void> { 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' }] this.remainingAuthMethods = [{ type: 'none' }]
if (!this.profile.options.auth || this.profile.options.auth === 'publicKey') { if (!this.profile.options.auth || this.profile.options.auth === 'publicKey') {
if (this.profile.options.privateKeys?.length) { if (this.profile.options.privateKeys?.length) {
@@ -164,192 +167,183 @@ export class SSHSession {
} }
} }
} }
if (!this.profile.options.auth || this.profile.options.auth === 'agent') { if (!this.profile.options.auth || this.profile.options.auth === 'agent') {
const spec = await this.getAgentConnectionSpec() if (!this.agentPath) {
if (!spec) { this.emitServiceMessage(colors.bgYellow.yellow.black(' ! ') + ` Agent auth selected, but no running agent is detected`)
this.emitServiceMessage(colors.bgYellow.yellow.black(' ! ') + ` Agent auth selected, but no running Agent process is found`)
} else { } else {
this.remainingAuthMethods.push({ this.remainingAuthMethods.push({ type: 'agent' })
type: 'agent',
...spec,
})
} }
} }
if (!this.profile.options.auth || this.profile.options.auth === 'password') { if (!this.profile.options.auth || this.profile.options.auth === 'password') {
if (this.profile.options.password) { this.remainingAuthMethods.push({ type: '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' })
} }
if (!this.profile.options.auth || this.profile.options.auth === 'keyboardInteractive') { 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: 'keyboard-interactive' })
} }
this.remainingAuthMethods.push({ type: 'hostbased' }) 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> { async openSFTP (): Promise<SFTPSession> {
if (!(this.ssh instanceof russh.AuthenticatedSSHClient)) {
throw new Error('Cannot open SFTP session before auth')
}
if (!this.sftp) { 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) return new SFTPSession(this.sftp, this.injector)
} }
async start (): Promise<void> { async start (): Promise<void> {
const log = (s: any) => this.emitServiceMessage(s)
const ssh = new Client()
this.ssh = ssh
await this.init() await this.init()
let connected = false
const algorithms = {} const algorithms = {}
for (const key of Object.values(SSHAlgorithmType)) { for (const key of Object.values(SSHAlgorithmType)) {
algorithms[key] = this.profile.options.algorithms![key].filter(x => supportedAlgorithms[key].includes(x)) algorithms[key] = this.profile.options.algorithms![key].filter(x => supportedAlgorithms[key].includes(x))
} }
// eslint-disable-next-line @typescript-eslint/init-declarations const hostVerifiedPromise: Promise<void> = new Promise((resolve, reject) => {
let transport: russh.SshTransport ssh.on('handshake', async handshake => {
if (this.profile.options.proxyCommand) { if (!await this.verifyHostKey(handshake)) {
this.emitServiceMessage(colors.bgBlue.black(' Proxy command ') + ` Using ${this.profile.options.proxyCommand}`) this.ssh.end()
reject(new Error('Host key verification failed'))
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
} }
this.logger.info('Host key verified') this.logger.info('Handshake complete:', handshake)
return true resolve()
}, })
{ })
preferred: {
ciphers: this.profile.options.algorithms?.[SSHAlgorithmType.CIPHER]?.filter(x => supportedAlgorithms[SSHAlgorithmType.CIPHER].includes(x)), const resultPromise: Promise<void> = new Promise(async (resolve, reject) => {
kex: this.profile.options.algorithms?.[SSHAlgorithmType.KEX]?.filter(x => supportedAlgorithms[SSHAlgorithmType.KEX].includes(x)), ssh.on('ready', () => {
mac: this.profile.options.algorithms?.[SSHAlgorithmType.HMAC]?.filter(x => supportedAlgorithms[SSHAlgorithmType.HMAC].includes(x)), connected = true
key: this.profile.options.algorithms?.[SSHAlgorithmType.HOSTKEY]?.filter(x => supportedAlgorithms[SSHAlgorithmType.HOSTKEY].includes(x)), // Fix SSH Lagging
}, ssh.setNoDelay(true)
keepaliveIntervalSeconds: Math.round((this.profile.options.keepaliveInterval ?? 15000) / 1000), 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, keepaliveCountMax: this.profile.options.keepaliveCountMax,
connectionTimeoutSeconds: this.profile.options.readyTimeout ? Math.round(this.profile.options.readyTimeout / 1000) : undefined, readyTimeout: this.profile.options.readyTimeout,
}, hostVerifier: (key: any) => {
) this.hostKeyDigest = crypto.createHash('sha256').update(key).digest('base64')
return true
this.ssh.banner$.subscribe(banner => { },
if (!this.profile.options.skipBanner) { algorithms,
this.emitServiceMessage(banner) authHandler: (methodsLeft, partialSuccess, callback) => {
} this.zone.run(async () => {
}) callback(await this.handleAuth(methodsLeft))
})
this.ssh.disconnect$.subscribe(() => { },
if (this.open) { })
this.destroy() } catch (e) {
} this.notifications.error(e.message)
}) throw e
// 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'
}
} }
if (this.authUsername?.startsWith('$')) { await resultPromise
try { await hostVerifiedPromise
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)
}
for (const fw of this.profile.options.forwardedPorts ?? []) { for (const fw of this.profile.options.forwardedPorts ?? []) {
this.addPortForward(Object.assign(new ForwardedPort(), fw)) this.addPortForward(Object.assign(new ForwardedPort(), fw))
@@ -357,11 +351,12 @@ export class SSHSession {
this.open = true this.open = true
this.ssh.tcpChannelOpen$.subscribe(async event => { this.ssh.on('tcp connection', (details, accept, reject) => {
this.logger.info(`Incoming forwarded connection: ${event.clientAddress}:${event.clientPort} -> ${event.targetAddress}:${event.targetPort}`) this.logger.info(`Incoming forwarded connection: (remote) ${details.srcIP}:${details.srcPort} -> (local) ${details.destIP}:${details.destPort}`)
const forward = this.forwardedPorts.find(x => x.port === event.targetPort && x.host === event.targetAddress) const forward = this.forwardedPorts.find(x => x.port === details.destPort)
if (!forward) { 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 return
} }
const socket = new Socket() const socket = new Socket()
@@ -369,19 +364,24 @@ export class SSHSession {
socket.on('error', e => { socket.on('error', e => {
// eslint-disable-next-line @typescript-eslint/no-base-to-string // 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}`) 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', () => { socket.on('connect', () => {
this.logger.info('Connection forwarded') 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.ssh.on('x11', async (details, accept, reject) => {
this.logger.info(`Incoming X11 connection from ${event.clientAddress}:${event.clientPort}`) this.logger.info(`Incoming X11 connection from ${details.srcIP}:${details.srcPort}`)
const displaySpec = (this.config.store.ssh.x11Display || process.env.DISPLAY) ?? 'localhost:0' const displaySpec = (this.config.store.ssh.x11Display || process.env.DISPLAY) ?? 'localhost:0'
this.logger.debug(`Trying display ${displaySpec}`) this.logger.debug(`Trying display ${displaySpec}`)
@@ -389,18 +389,14 @@ export class SSHSession {
try { try {
const x11Stream = await socket.connect(displaySpec) const x11Stream = await socket.connect(displaySpec)
this.logger.info('Connection forwarded') this.logger.info('Connection forwarded')
const stream = accept()
event.channel.data$.subscribe(data => { stream.pipe(x11Stream)
x11Stream.write(data) x11Stream.pipe(stream)
}) stream.on('close', () => {
x11Stream.on('data', data => {
event.channel.write(Uint8Array.from(data))
})
event.channel.closed$.subscribe(() => {
socket.destroy() socket.destroy()
}) })
x11Stream.on('close', () => { x11Stream.on('close', () => {
event.channel.close() stream.close()
}) })
} catch (e) { } catch (e) {
// eslint-disable-next-line @typescript-eslint/no-base-to-string // eslint-disable-next-line @typescript-eslint/no-base-to-string
@@ -411,43 +407,27 @@ export class SSHSession {
this.emitServiceMessage(' * VcXsrv: https://sourceforge.net/projects/vcxsrv/') this.emitServiceMessage(' * VcXsrv: https://sourceforge.net/projects/vcxsrv/')
this.emitServiceMessage(' * Xming: https://sourceforge.net/projects/xming/') 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('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) { if (!this.config.store.ssh.verifyHostKeys) {
return true return true
} }
const selector = { const selector = {
host: this.profile.options.host, host: this.profile.options.host,
port: this.profile.options.port ?? 22, 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) const knownHost = this.knownHosts.getFor(selector)
if (!knownHost || knownHost.digest !== keyDigest) { if (!knownHost || knownHost.digest !== this.hostKeyDigest) {
const modal = this.ngbModal.open(HostKeyPromptModalComponent) const modal = this.ngbModal.open(HostKeyPromptModalComponent)
modal.componentInstance.selector = selector modal.componentInstance.selector = selector
modal.componentInstance.digest = keyDigest modal.componentInstance.digest = this.hostKeyDigest
return modal.result.catch(() => false) return modal.result.catch(() => false)
} }
return true return true
@@ -469,49 +449,57 @@ export class SSHSession {
this.keyboardInteractivePrompt.next(prompt) this.keyboardInteractivePrompt.next(prompt)
} }
async handleAuth (methodsLeft?: string[] | null): Promise<russh.AuthenticatedSSHClient|null> { async handleAuth (methodsLeft?: string[] | null): Promise<any> {
this.activePrivateKey = null 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) { while (true) {
const method = this.remainingAuthMethods.shift() const method = this.remainingAuthMethods.shift()
if (!method) { if (!method) {
return null return false
} }
if (methodsLeft && !methodsLeft.includes(method.type) && method.type !== 'agent') { if (methodsLeft && !methodsLeft.includes(method.type) && method.type !== 'agent') {
// Agent can still be used even if not in methodsLeft // Agent can still be used even if not in methodsLeft
this.logger.info('Server does not support auth method', method.type) this.logger.info('Server does not support auth method', method.type)
continue continue
} }
if (method.type === 'saved-password') { if (method.type === 'password') {
this.emitServiceMessage(this.translate.instant('Using saved password')) if (this.profile.options.password) {
const result = await this.ssh.authenticateWithPassword(this.authUsername, method.password) this.emitServiceMessage(this.translate.instant('Using preset password'))
if (result) { return {
return result 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) const modal = this.ngbModal.open(PromptModalComponent)
modal.componentInstance.prompt = `Password for ${this.authUsername}@${this.profile.options.host}` modal.componentInstance.prompt = `Password for ${this.authUsername}@${this.profile.options.host}`
modal.componentInstance.password = true modal.componentInstance.password = true
modal.componentInstance.showRememberCheckbox = true modal.componentInstance.showRememberCheckbox = true
try { try {
const promptResult = await modal.result.catch(() => null) const result = await modal.result.catch(() => null)
if (promptResult) { if (result) {
if (promptResult.remember) { if (result.remember) {
this.savedPassword = promptResult.value this.savedPassword = result.value
} }
const result = await this.ssh.authenticateWithPassword(this.authUsername, promptResult.value) return {
if (result) { type: 'password',
return result username: this.authUsername,
password: result.value,
} }
} else { } else {
continue continue
@@ -520,104 +508,50 @@ export class SSHSession {
continue continue
} }
} }
if (method.type === 'publickey') { if (method.type === 'publickey' && method.contents) {
try { try {
const key = await this.loadPrivateKey(method.name, method.contents) const key = await this.loadPrivateKey(method.name!, method.contents)
const result = await this.ssh.authenticateWithKeyPair(this.authUsername, key) return {
if (result) { type: 'publickey',
return result username: this.authUsername,
key,
} }
} catch (e) { } catch (e) {
this.emitServiceMessage(colors.bgYellow.yellow.black(' ! ') + ` Failed to load private key ${method.name}: ${e}`) this.emitServiceMessage(colors.bgYellow.yellow.black(' ! ') + ` Failed to load private key ${method.name}: ${e}`)
continue continue
} }
} }
if (method.type === 'keyboard-interactive') { return method.type
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 null
} }
async addPortForward (fw: ForwardedPort): Promise<void> { async addPortForward (fw: ForwardedPort): Promise<void> {
if (fw.type === PortForwardType.Local || fw.type === PortForwardType.Dynamic) { 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}`) this.logger.info(`New connection on ${fw}`)
if (!(this.ssh instanceof russh.AuthenticatedSSHClient)) { this.ssh.forwardOut(
this.logger.error(`Connection while unauthenticated on ${fw}`) sourceAddress ?? '127.0.0.1',
reject() sourcePort ?? 0,
return targetAddress,
} targetPort,
const channel = await this.ssh.openTCPForwardChannel({ (err, stream) => {
addressToConnectTo: targetAddress, if (err) {
portToConnectTo: targetPort, // eslint-disable-next-line @typescript-eslint/no-base-to-string
originatorAddress: sourceAddress ?? '127.0.0.1', this.emitServiceMessage(colors.bgRed.black(' X ') + ` Remote has rejected the forwarded connection to ${targetAddress}:${targetPort} via ${fw}: ${err}`)
originatorPort: sourcePort ?? 0, reject()
}).catch(err => { return
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Remote has rejected the forwarded connection to ${targetAddress}:${targetPort} via ${fw}: ${err}`) }
reject() const socket = accept()
throw err stream.pipe(socket)
}) socket.pipe(stream)
const socket = accept() stream.on('close', () => {
channel.data$.subscribe(data => socket.write(data)) socket.destroy()
socket.on('data', data => channel.write(Uint8Array.from(data))) })
channel.closed$.subscribe(() => socket.destroy()) socket.on('close', () => {
socket.on('close', () => channel.close()) stream.close()
})
},
)
}).then(() => { }).then(() => {
this.emitServiceMessage(colors.bgGreen.black(' -> ') + ` Forwarded ${fw}`) this.emitServiceMessage(colors.bgGreen.black(' -> ') + ` Forwarded ${fw}`)
this.forwardedPorts.push(fw) this.forwardedPorts.push(fw)
@@ -627,16 +561,17 @@ export class SSHSession {
}) })
} }
if (fw.type === PortForwardType.Remote) { if (fw.type === PortForwardType.Remote) {
if (!(this.ssh instanceof russh.AuthenticatedSSHClient)) { await new Promise<void>((resolve, reject) => {
throw new Error('Cannot add remote port forward before auth') this.ssh.forwardIn(fw.host, fw.port, err => {
} if (err) {
try { // eslint-disable-next-line @typescript-eslint/no-base-to-string
await this.ssh.forwardTCPPort(fw.host, fw.port) this.emitServiceMessage(colors.bgRed.black(' X ') + ` Remote rejected port forwarding for ${fw}: ${err}`)
} catch (err) { reject(err)
// eslint-disable-next-line @typescript-eslint/no-base-to-string return
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Remote rejected port forwarding for ${fw}: ${err}`) }
return resolve()
} })
})
this.emitServiceMessage(colors.bgGreen.black(' <- ') + ` Forwarded ${fw}`) this.emitServiceMessage(colors.bgGreen.black(' <- ') + ` Forwarded ${fw}`)
this.forwardedPorts.push(fw) this.forwardedPorts.push(fw)
} }
@@ -648,10 +583,7 @@ export class SSHSession {
this.forwardedPorts = this.forwardedPorts.filter(x => x !== fw) this.forwardedPorts = this.forwardedPorts.filter(x => x !== fw)
} }
if (fw.type === PortForwardType.Remote) { if (fw.type === PortForwardType.Remote) {
if (!(this.ssh instanceof russh.AuthenticatedSSHClient)) { this.ssh.unforwardIn(fw.host, fw.port)
throw new Error('Cannot remove remote port forward before auth')
}
this.ssh.stopForwardingTCPPort(fw.host, fw.port)
this.forwardedPorts = this.forwardedPorts.filter(x => x !== fw) this.forwardedPorts = this.forwardedPorts.filter(x => x !== fw)
} }
this.emitServiceMessage(`Stopped forwarding ${fw}`) this.emitServiceMessage(`Stopped forwarding ${fw}`)
@@ -662,55 +594,43 @@ export class SSHSession {
this.willDestroy.next() this.willDestroy.next()
this.willDestroy.complete() this.willDestroy.complete()
this.serviceMessage.complete() this.serviceMessage.complete()
this.ssh.disconnect() this.proxyCommandStream?.stop()
this.ssh.end()
} }
async openShellChannel (options: { x11: boolean }): Promise<russh.Channel> { openShellChannel (options: { x11: boolean }): Promise<ClientChannel> {
if (!(this.ssh instanceof russh.AuthenticatedSSHClient)) { return new Promise<ClientChannel>((resolve, reject) => {
throw new Error('Cannot open shell channel before auth') this.ssh.shell({ term: 'xterm-256color' }, options, (err, shell) => {
} if (err) {
const ch = await this.ssh.openSessionChannel() reject(err)
await ch.requestPTY('xterm-256color', { } else {
columns: 80, resolve(shell)
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,
}) })
} })
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.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 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') const keyHash = crypto.createHash('sha512').update(privateKey).digest('hex')
let triedSavedPassphrase = false let triedSavedPassphrase = false
let passphrase: string|null = null let passphrase: string|null = null
while (true) { while (true) {
try { try {
return await russh.KeyPair.parse(privateKey, passphrase ?? undefined) return sshpk.parsePrivateKey(privateKey, 'auto', { passphrase })
} catch (e) { } catch (e) {
if (!triedSavedPassphrase) { if (!triedSavedPassphrase) {
passphrase = await this.passwordStorage.loadPrivateKeyPassword(keyHash) passphrase = await this.passwordStorage.loadPrivateKeyPassword(keyHash)
triedSavedPassphrase = true triedSavedPassphrase = true
continue 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) await this.passwordStorage.deletePrivateKeyPassword(keyHash)
const modal = this.ngbModal.open(PromptModalComponent) const modal = this.ngbModal.open(PromptModalComponent)

View File

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

View File

@@ -9,11 +9,26 @@
dependencies: dependencies:
ipv6 "*" ipv6 "*"
"@types/node@20.3.1": "@types/node@*", "@types/node@20.3.1":
version "20.3.1" version "20.3.1"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.1.tgz#e8a83f1aa8b649377bb1fb5d7bac5cb90e784dfe" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.1.tgz#e8a83f1aa8b649377bb1fb5d7bac5cb90e784dfe"
integrity sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg== 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: ansi-colors@^4.1.1:
version "4.1.3" version "4.1.3"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" 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" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a"
integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== 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: async@0.2.x:
version "0.2.10" version "0.2.10"
resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1" 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: balanced-match@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 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: brace-expansion@^1.1.7:
version "1.1.11" version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 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" balanced-match "^1.0.0"
concat-map "0.0.1" 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: cli@0.4.x:
version "0.4.5" version "0.4.5"
resolved "https://registry.yarnpkg.com/cli/-/cli-0.4.5.tgz#78f9485cd161b566e9a6c72d7170c4270e81db61" resolved "https://registry.yarnpkg.com/cli/-/cli-0.4.5.tgz#78f9485cd161b566e9a6c72d7170c4270e81db61"
integrity sha512-dbn5HyeJWSOU58RwOEiF1VWrl7HRvDsKLpu0uiI/vExH6iNoyUzjB5Mr3IJY5DVUfnbpe9793xw4DFJVzC9nWQ== integrity sha1-ePlIXNFhtWbppsctcXDEJw6B22E=
dependencies: dependencies:
glob ">= 3.1.4" glob ">= 3.1.4"
cliff@0.1.x: cliff@0.1.x:
version "0.1.10" version "0.1.10"
resolved "https://registry.yarnpkg.com/cliff/-/cliff-0.1.10.tgz#53be33ea9f59bec85609ee300ac4207603e52013" resolved "https://registry.yarnpkg.com/cliff/-/cliff-0.1.10.tgz#53be33ea9f59bec85609ee300ac4207603e52013"
integrity sha512-roZWcC2Cxo/kKjRXw7YUpVNtxJccbvcl7VzTjUYgLQk6Ot0R8bm2netbhSZYWWNrKlOO/7HD6GXHl8dtzE6SiQ== integrity sha1-U74z6p9ZvshWCe4wCsQgdgPlIBM=
dependencies: dependencies:
colors "~1.0.3" colors "~1.0.3"
eyes "~0.1.8" eyes "~0.1.8"
@@ -61,12 +105,12 @@ cliff@0.1.x:
colors@0.6.x: colors@0.6.x:
version "0.6.2" version "0.6.2"
resolved "https://registry.yarnpkg.com/colors/-/colors-0.6.2.tgz#2423fe6678ac0c5dae8852e5d0e5be08c997abcc" 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: colors@~1.0.3:
version "1.0.3" version "1.0.3"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" 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: concat-map@0.0.1:
version "0.0.1" version "0.0.1"
@@ -76,19 +120,62 @@ concat-map@0.0.1:
cycle@1.0.x: cycle@1.0.x:
version "1.0.3" version "1.0.3"
resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2" 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: eyes@0.1.x, eyes@~0.1.8:
version "0.1.8" version "0.1.8"
resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0" 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: fs.realpath@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 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" version "7.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
@@ -116,7 +203,7 @@ inherits@2:
ipv6@*: ipv6@*:
version "3.1.3" version "3.1.3"
resolved "https://registry.yarnpkg.com/ipv6/-/ipv6-3.1.3.tgz#4d9064f9c2dafa0dd10b8b7d76ffca4aad31b3b9" resolved "https://registry.yarnpkg.com/ipv6/-/ipv6-3.1.3.tgz#4d9064f9c2dafa0dd10b8b7d76ffca4aad31b3b9"
integrity sha512-TmLbUIURMAZ161GZDddTtAAb3aceRNLn7PRmP8fANp8xDRCW9oIQva8eenA48bRvw347jBqSREXMI38DybbUiQ== integrity sha1-TZBk+cLa+g3RC4t9dv/KSq0xs7k=
dependencies: dependencies:
cli "0.4.x" cli "0.4.x"
cliff "0.1.x" cliff "0.1.x"
@@ -125,7 +212,27 @@ ipv6@*:
isstream@0.1.x: isstream@0.1.x:
version "0.1.2" version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" 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: minimatch@^3.1.1:
version "3.1.2" version "3.1.2"
@@ -149,7 +256,14 @@ path-is-absolute@^1.0.0:
pkginfo@0.3.x: pkginfo@0.3.x:
version "0.3.1" version "0.3.1"
resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.3.1.tgz#5b29f6a81f70717142e09e765bbeab97b4f81e21" 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: rimraf@^3.0.0:
version "3.0.2" 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" resolved "https://registry.yarnpkg.com/run-script-os/-/run-script-os-1.1.6.tgz#8b0177fb1b54c99a670f95c7fdc54f18b9c72347"
integrity sha512-ql6P2LzhBTTDfzKts+Qo4H94VUKpxKDFz6QxxwaUZN0mwvi7L3lpOI7BqPCq7lgDh3XLl0dpeXwfcVIitlrYrw== 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: sprintf@0.1.x:
version "0.1.5" version "0.1.5"
resolved "https://registry.yarnpkg.com/sprintf/-/sprintf-0.1.5.tgz#8f83e39a9317c1a502cb7db8050e51c679f6edcf" 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: stack-trace@0.0.x:
version "0.0.10" version "0.0.10"
resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" 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: strip-ansi@^7.0.0:
version "7.1.0" version "7.1.0"
@@ -194,10 +332,15 @@ tmp@^0.2.0:
dependencies: dependencies:
rimraf "^3.0.0" 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: winston@0.8.x:
version "0.8.3" version "0.8.3"
resolved "https://registry.yarnpkg.com/winston/-/winston-0.8.3.tgz#64b6abf4cd01adcaefd5009393b1d8e8bec19db0" resolved "https://registry.yarnpkg.com/winston/-/winston-0.8.3.tgz#64b6abf4cd01adcaefd5009393b1d8e8bec19db0"
integrity sha512-fPoamsHq8leJ62D1M9V/f15mjQ1UHe4+7j1wpAT3fqgA5JqhJkk4aIfPEjfMTI9x6ZTjaLOpMAjluLtmgO5b6g== integrity sha1-ZLar9M0Brcrv1QCTk7HY6L7BnbA=
dependencies: dependencies:
async "0.2.x" async "0.2.x"
colors "0.6.x" colors "0.6.x"

View File

@@ -13,7 +13,6 @@ import { ResizeEvent, BaseTerminalProfile } from './interfaces'
import { TerminalDecorator } from './decorator' import { TerminalDecorator } from './decorator'
import { SearchPanelComponent } from '../components/searchPanel.component' import { SearchPanelComponent } from '../components/searchPanel.component'
import { MultifocusService } from '../services/multifocus.service' import { MultifocusService } from '../services/multifocus.service'
import { getTerminalBackgroundColor } from '../helpers'
const INACTIVE_TAB_UNLOAD_DELAY = 1000 * 30 const INACTIVE_TAB_UNLOAD_DELAY = 1000 * 30
@@ -576,7 +575,14 @@ export class BaseTerminalTabComponent<P extends BaseTerminalProfile> extends Bas
configure (): void { configure (): void {
this.frontend?.configure(this.profile) 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 { zoomIn (): void {

View File

@@ -1,4 +1,3 @@
import deepEqual from 'deep-equal'
import { BehaviorSubject, filter, firstValueFrom, takeUntil } from 'rxjs' import { BehaviorSubject, filter, firstValueFrom, takeUntil } from 'rxjs'
import { Injector } from '@angular/core' import { Injector } from '@angular/core'
import { ConfigService, getCSSFontFamily, getWindows10Build, HostAppService, HotkeysService, Platform, PlatformService, ThemesService } from 'tabby-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 { SerializeAddon } from '@xterm/addon-serialize'
import { ImageAddon } from '@xterm/addon-image' import { ImageAddon } from '@xterm/addon-image'
import { CanvasAddon } from '@xterm/addon-canvas' import { CanvasAddon } from '@xterm/addon-canvas'
import { BaseTerminalProfile, TerminalColorScheme } from '../api/interfaces'
import { getTerminalBackgroundColor } from '../helpers'
import './xterm.css' import './xterm.css'
import deepEqual from 'deep-equal'
import { BaseTerminalProfile, TerminalColorScheme } from '../api/interfaces'
const COLOR_NAMES = [ const COLOR_NAMES = [
'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white',
@@ -362,21 +361,21 @@ export class XTermFrontend extends Frontend {
} }
private configureColors (scheme: TerminalColorScheme|undefined): void { 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 = { const theme: ITheme = {
foreground: scheme.foreground, foreground: scheme!.foreground,
selectionBackground: scheme.selection ?? '#88888888', selectionBackground: scheme!.selection ?? '#88888888',
selectionForeground: scheme.selectionForeground ?? undefined, selectionForeground: scheme!.selectionForeground ?? undefined,
background: getTerminalBackgroundColor(this.configService, this.themes, scheme) ?? '#00000000', background: !this.themes.findCurrentTheme().followsColorScheme && config.terminal.background === 'colorScheme' ? scheme!.background : '#00000000',
cursor: scheme.cursor, cursor: scheme!.cursor,
cursorAccent: scheme.cursorAccent, cursorAccent: scheme!.cursorAccent,
} }
for (let i = 0; i < COLOR_NAMES.length; i++) { 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)) { 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.fontFamily = getCSSFontFamily(config)
this.xterm.options.cursorStyle = { this.xterm.options.cursorStyle = {
beam: 'bar', 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 copyToClipboard from 'copy-text-to-clipboard'
import { Injectable, Inject } from '@angular/core' import { Injectable, Inject } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap' 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 // eslint-disable-next-line no-duplicate-imports
import type { ContextMenuElement, ContextMenuItem } from '@vaadin/vaadin-context-menu' 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 { setErrorHandler (handler: (_: any) => void): void {
window.addEventListener('error', handler) window.addEventListener('error', handler)
} }
@@ -149,7 +145,7 @@ export class WebPlatformService extends PlatformService {
} }
class HTMLFileDownload extends FileDownload { class HTMLFileDownload extends FileDownload {
private buffers: Uint8Array[] = [] private buffers: Buffer[] = []
constructor ( constructor (
private name: string, private name: string,
@@ -171,8 +167,8 @@ class HTMLFileDownload extends FileDownload {
return this.size return this.size
} }
async write (buffer: Uint8Array): Promise<void> { async write (buffer: Buffer): Promise<void> {
this.buffers.push(Uint8Array.from(buffer)) this.buffers.push(Buffer.from(buffer))
this.increaseProgress(buffer.length) this.increaseProgress(buffer.length)
if (this.isComplete()) { if (this.isComplete()) {
this.finish() this.finish()

View File

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

View File

@@ -863,12 +863,10 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.1.tgz#e8a83f1aa8b649377bb1fb5d7bac5cb90e784dfe" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.1.tgz#e8a83f1aa8b649377bb1fb5d7bac5cb90e784dfe"
integrity sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg== integrity sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==
"@types/node@^20.9.0": "@types/node@^18.11.18":
version "20.14.14" version "18.17.12"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.14.tgz#6b655d4a88623b0edb98300bb9dd2107225f885e" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.17.12.tgz#c6bd7413a13e6ad9cfb7e97dd5c4e904c1821e50"
integrity sha512-d64f00982fS9YoOgJkAMolK7MN8Iq3TDdVjchbYHdEmjth/DHowx82GnoA+tVUAN+7vxfYUgAzi+JXbKNd2SDQ== integrity sha512-d6xjC9fJ/nSnfDeU0AMDsaJyb1iHsqCSOdi84w4u+SlN/UgQdY5tRhpMzaFYsI4mnpvgTivEaQd0yOUhAtOnEQ==
dependencies:
undici-types "~5.26.4"
"@types/parse5@^5": "@types/parse5@^5":
version "5.0.3" version "5.0.3"
@@ -1553,6 +1551,13 @@ asn1.js@^5.2.0:
minimalistic-assert "^1.0.0" minimalistic-assert "^1.0.0"
safer-buffer "^2.1.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: asn1@~0.2.3:
version "0.2.4" version "0.2.4"
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" 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" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
bcrypt-pbkdf@^1.0.0: bcrypt-pbkdf@^1.0.0, bcrypt-pbkdf@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=
@@ -1907,6 +1912,11 @@ buffer@^5.1.0:
base64-js "^1.3.1" base64-js "^1.3.1"
ieee754 "^1.1.13" 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: builder-util-runtime@9.2.1:
version "9.2.1" version "9.2.1"
resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-9.2.1.tgz#3184dcdf7ed6c47afb8df733813224ced4f624fd" 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" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= 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: crc@^3.8.0:
version "3.8.0" version "3.8.0"
resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6" 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" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.286.tgz#0e039de59135f44ab9a8ec9025e53a9135eba11f"
integrity sha512-Vp3CVhmYpgf4iXNKAucoQUDcCrBQX3XLBtwgFqP9BUXuucgvAV9zWp1kYU7LL9j4++s9O+12cb3wMtN4SJy6UQ== integrity sha512-Vp3CVhmYpgf4iXNKAucoQUDcCrBQX3XLBtwgFqP9BUXuucgvAV9zWp1kYU7LL9j4++s9O+12cb3wMtN4SJy6UQ==
electron@^29: electron@^27.0.4:
version "29.4.5" version "27.1.0"
resolved "https://registry.yarnpkg.com/electron/-/electron-29.4.5.tgz#b83bbeee6fc722dbbaab30d3a6bc8e982c9ab98d" resolved "https://registry.yarnpkg.com/electron/-/electron-27.1.0.tgz#d759885e552d7d926526cfc433ab312796f74a9a"
integrity sha512-DlEuzGbWBYl1Qr0qUYgNZdoixJg4YGHy2HC6fkRjSXSlb01UrQ5ORi8hNLzelzyYx8rNQyyE3zDUuk9EnZwYuA== integrity sha512-XPdJiO475QJ8cx59/goWNNWnlV0vab+Ut3occymos7VDxkHV5mFrlW6tcGi+M3bW6gBfwpJocWMng8tw542vww==
dependencies: dependencies:
"@electron/get" "^2.0.0" "@electron/get" "^2.0.0"
"@types/node" "^20.9.0" "@types/node" "^18.11.18"
extract-zip "^2.0.1" extract-zip "^2.0.1"
elliptic@^6.5.3: elliptic@^6.5.3:
@@ -5603,10 +5621,10 @@ lzma-native@^8.0.5, lzma-native@^8.0.6:
node-gyp-build "^4.2.1" node-gyp-build "^4.2.1"
readable-stream "^3.6.0" readable-stream "^3.6.0"
macos-release@^3.3.0: macos-release@^3.1.0:
version "3.3.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-3.3.0.tgz#92cb67bc66d67c3fde4a9e14f5f909afa418b072" resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-3.1.0.tgz#6165bb0736ae567ed6649e36ce6a24d87cbb7aca"
integrity sha512-tPJQ1HeyiU2vRruNGhZ+VleWuMQRro8iFtJxYgnS4NQe+EukKF6aGiIT+7flZhISAt2iaXBCfFGvAyif7/f8nQ== integrity sha512-/M/R0gCDgM+Cv1IuBG1XGdfTFnMEG6PZeT+KGWHO/OG+imqmaD9CH5vHBTycEM3+Kc4uG2Il+tFAuUWLqQOeUA==
magic-string@^0.27.0: magic-string@^0.27.0:
version "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" resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz"
integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=
nan@2.17.0: nan@2.17.0, nan@^2.17.0:
version "2.17.0" version "2.17.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb" resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb"
integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ== integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==
@@ -6010,10 +6028,10 @@ no-case@^3.0.4:
lower-case "^2.0.2" lower-case "^2.0.2"
tslib "^2.0.3" tslib "^2.0.3"
node-abi@^3.0.0, node-abi@^3.65.0: node-abi@^3.0.0, node-abi@^3.51.0:
version "3.65.0" version "3.51.0"
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.65.0.tgz#ca92d559388e1e9cab1680a18c1a18757cdac9d3" resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.51.0.tgz#970bf595ef5a26a271307f8a4befa02823d4e87d"
integrity sha512-ThjYBfoDNr08AWx6hGaRbfPwxKV9kVzAzOzlLKbk2CuqXE2xnCh+cbAGnwM3t8Lq4v9rUB7VfondlkBckcJrVA== integrity sha512-SQkEP4hmNWjlniS5zdnfIXTk1x7Ome85RDzHlTbBtzE97Gfwz/Ipw4v/Ryk20DWIy3yCNVLVlGKApCnmvYoJbA==
dependencies: dependencies:
semver "^7.3.5" 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" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= 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: sshpk@^1.7.0:
version "1.16.1" version "1.16.1"
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" 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" has-symbols "^1.0.3"
which-boxed-primitive "^1.0.2" 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: unique-filename@^1.1.0, unique-filename@~1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.0.tgz" resolved "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.0.tgz"