Compare commits

..

48 Commits

Author SHA1 Message Date
Eugene
8d715ab36b Merge branch 'master' into russh 2024-09-23 09:35:18 +02:00
Eugene
83794ede72 Update README.md 2024-09-04 17:02:58 +02:00
Eugene
c3df6be8c7 fixed #1790 - remember answers to password prompts in keyboard-interactive authentication 2024-08-31 22:09:35 +02:00
Eugene
b0fbc33963 Merge branch 'master' into russh 2024-08-24 23:49:51 +02:00
Eugene
2a9d5816fc keepalive and timeout 2024-08-24 23:41:19 +02:00
Eugene
1dc6a627fd fixed #9830 - add ssh-rsa to default algos 2024-08-24 12:38:23 +02:00
Eugene
2671e55fbf glob lock 2024-08-23 00:38:21 +02:00
Eugene
49b70680c8 Update yarn.lock 2024-08-23 00:34:12 +02:00
Eugene
054e1948a5 bump 2024-08-23 00:06:36 +02:00
Eugene
4470851d12 fixes 2024-08-22 23:52:13 +02:00
Eugene
f369998452 cleanup 2024-08-21 20:41:03 +02:00
Eugene
581d7a66c3 proxy support 2024-08-21 20:38:19 +02:00
Eugene
3aa9aad854 agent forwarding 2024-08-20 23:55:56 +02:00
Eugene
b0e0709a36 lint 2024-08-20 09:12:18 +02:00
Eugene
b5975f045a windows ssh agent auth 2024-08-19 21:10:36 +02:00
Eugene
b9c6bbf748 Update ssh.ts 2024-08-16 20:08:21 +02:00
Eugene
a1e54b8410 agent auth 2024-08-15 21:52:58 +02:00
Eugene
c42ea8ae08 bumped russh 2024-08-15 00:06:41 +02:00
Eugene
a3cc85627d jump hosts 2024-08-03 23:28:46 +02:00
Eugene
8928fa1737 disconnect detection 2024-08-03 23:01:52 +02:00
Eugene
ca2cf0ffa0 passphrase handling / cleanup 2024-08-03 22:07:13 +02:00
Eugene
76d72a5f32 Merge branch 'master' into russh 2024-08-03 21:45:40 +02:00
Eugene
7c2793f157 Update build.yml 2024-07-23 09:17:06 +02:00
Eugene
6af8469ac6 wip 2024-07-23 08:45:09 +02:00
Eugene
1bf8fdc339 wjl 2024-07-23 00:31:34 +02:00
Eugene
a2b80ff742 Update build.yml 2024-07-22 11:10:28 +02:00
Eugene
0a94c971cf wip 2024-07-22 11:10:02 +02:00
Eugene
e4d896f02e Update build.yml 2024-07-22 10:44:32 +02:00
Eugene
4330317dea Update build.yml 2024-07-22 10:25:53 +02:00
Eugene
f1fed5feb6 Update build.yml 2024-07-22 10:10:48 +02:00
Eugene
6e1d8868f8 Update build.yml 2024-07-22 09:55:09 +02:00
Eugene
8514f3308b Update build.yml 2024-07-22 09:41:14 +02:00
Eugene
c2d437d407 . 2024-07-22 09:05:27 +02:00
Eugene
237f78edf7 Update build.yml 2024-07-22 08:48:31 +02:00
Eugene
a4438d74e4 Update build.yml 2024-07-22 01:01:12 +02:00
Eugene
0634eb4cee Update build.yml 2024-07-21 20:25:56 +02:00
Eugene
8550f95a84 Update build.yml 2024-07-21 19:52:22 +02:00
Eugene
b45f74ff83 Update build.yml 2024-07-21 19:13:15 +02:00
Eugene
a378b94324 wip 2024-07-21 19:03:36 +02:00
Eugene
672f85e4d9 wip 2024-07-21 18:39:41 +02:00
Eugene
794a940d2b Update build.yml 2024-07-21 18:04:29 +02:00
Eugene
9c0352f9cc wip 2024-07-21 11:16:12 +02:00
Eugene
c2922c960b sftp file transfers 2024-07-20 14:28:59 +02:00
Eugene
ec8ccb5d43 Update sftp.ts 2024-07-15 22:56:18 +02:00
Eugene
1f2bf12ed7 sftp wip 2024-07-11 23:56:33 +02:00
Eugene
c8d5b7ab61 wip 2024-07-10 23:01:05 +02:00
Eugene
d265b5f8ab x11 2024-07-10 20:11:03 +02:00
Eugene
a01d693eec wip 2024-07-09 21:34:01 +02:00
92 changed files with 2584 additions and 2391 deletions

View File

@@ -1328,33 +1328,6 @@
"contributions": [
"code"
]
},
{
"login": "SelfHosted-Club",
"name": "SelfHosted",
"avatar_url": "https://avatars.githubusercontent.com/u/128927132?v=4",
"profile": "https://www.selfhosted.sg/",
"contributions": [
"financial"
]
},
{
"login": "xhiroga",
"name": "Hiroaki Ogasawara",
"avatar_url": "https://avatars.githubusercontent.com/u/13391129?v=4",
"profile": "http://hiroga.hatenablog.com/",
"contributions": [
"code"
]
},
{
"login": "geodic",
"name": "geodic",
"avatar_url": "https://avatars.githubusercontent.com/u/64704703?v=4",
"profile": "https://github.com/geodic",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,

View File

@@ -2,7 +2,7 @@ name: Package-Build
on: [push, pull_request]
jobs:
Lint:
runs-on: ubuntu-24.04
runs-on: ubuntu-20.04
steps:
- name: Checkout
@@ -13,11 +13,10 @@ jobs:
- name: Installing Node
uses: actions/setup-node@v3.7.0
with:
node-version: 22
node-version: 18
- name: Install deps
run: |
sudo apt-get update && sudo apt-get install -y libfontconfig1-dev
npm i -g yarn
cd app
yarn
@@ -32,7 +31,7 @@ jobs:
run: yarn run lint
macOS-Build:
runs-on: macos-15
runs-on: macos-12
needs: Lint
strategy:
matrix:
@@ -56,12 +55,14 @@ jobs:
- name: Installing Node
uses: actions/setup-node@v3.7.0
with:
node-version: 22
node-version: 18
- run: rustup target add ${{matrix.rust_triple}}
- name: Install deps
run: |
sudo -H pip3 install setuptools
sudo npm i -g yarn
yarn --network-timeout 1000000
env:
ARCH: ${{matrix.arch}}
@@ -81,7 +82,7 @@ jobs:
- name: Build and sign packages
run: scripts/build-macos.mjs
if: github.event_name == 'push' && (github.ref_protected || startsWith(github.ref, 'refs/tags'))
if: github.repository == 'Eugeny/tabby' && github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags'))
env:
ARCH: ${{matrix.arch}}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -96,7 +97,7 @@ jobs:
- name: Build packages without signing
run: scripts/build-macos.mjs
if: "! (github.event_name == 'push' && (github.ref_protected || startsWith(github.ref, 'refs/tags')))"
if: "! (github.repository == 'Eugeny/tabby' && github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags')))"
env:
ARCH: ${{matrix.arch}}
# DEBUG: electron-builder,electron-builder:*
@@ -130,7 +131,7 @@ jobs:
path: artifact-zip
Linux-Build:
runs-on: ${{matrix.os}}
runs-on: ubuntu-20.04
needs: Lint
strategy:
matrix:
@@ -138,17 +139,14 @@ jobs:
- build-arch: x64
arch: amd64
rust_triple: x86_64-unknown-linux-gnu
os: ubuntu-24.04
- build-arch: arm64
arch: arm64
rust_triple: aarch64-unknown-linux-gnu
triplet: aarch64-linux-gnu-
os: ubuntu-24.04-arm
- build-arch: arm
arch: armhf
rust_triple: arm-unknown-linux-gnueabihf
triplet: arm-linux-gnueabihf-
os: ubuntu-24.04
fail-fast: false
env:
@@ -168,27 +166,23 @@ jobs:
- name: Install Node
uses: actions/setup-node@v3.7.0
with:
node-version: 22
- name: Install FPM
run: |
sudo gem install fpm
node-version: 18
- run: rustup target add ${{matrix.rust_triple}}
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install libfontconfig1-dev libarchive-tools zsh crossbuild-essential-${{matrix.arch}}
sudo apt-get install libarchive-tools zsh crossbuild-essential-${{matrix.arch}}
- name: Setup tar to run as root
run: sudo chmod u+s "$(command -v tar)"
if: matrix.build-arch == 'arm'
if: matrix.build-arch != 'x64'
- name: Download cached sysroot
uses: actions/cache@v3
id: dl-cached-sysroot
if: matrix.build-arch == 'arm'
if: matrix.build-arch !='x64'
with:
key: sysroot-${{matrix.build-arch}}
path: /${{matrix.build-arch}}-sysroot
@@ -198,7 +192,7 @@ jobs:
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 == 'arm' && steps.dl-cached-sysroot.outputs.cache-hit != 'true'
if: matrix.build-arch != 'x64' && steps.dl-cached-sysroot.outputs.cache-hit != 'true'
- name: Setup env to use ${{matrix.build-arch}} sysroot
run: |
@@ -213,9 +207,9 @@ jobs:
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 == 'arm'
if: matrix.build-arch != 'x64'
- name: Install npm_modules (native)
- name: Install npm_modules (amd64)
run: |
npm i -g yarn node-gyp
yarn --network-timeout 1000000 --arch=${{matrix.build-arch}} --target-arch=${{matrix.build-arch}}
@@ -232,7 +226,6 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
KEYGEN_TOKEN: ${{ secrets.KEYGEN_TOKEN }}
USE_HARD_LINKS: false
USE_SYSTEM_FPM: true
# DEBUG: electron-builder,electron-builder:*
- name: Build web resources (amd64 only)
@@ -251,14 +244,14 @@ jobs:
- name: Upload packages to packagecloud.io
uses: TykTechnologies/packagecloud-action@main
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
if: github.repository == 'Eugeny/tabby' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
env:
PACKAGECLOUD_TOKEN: ${{ secrets.PACKAGECLOUD_TOKEN }}
with:
repo: 'eugeny/tabby'
dir: 'dist'
rpmvers: 'el/9 el/8 ol/6 ol/7'
debvers: 'ubuntu/bionic ubuntu/focal ubuntu/hirsute ubuntu/impish ubuntu/jammy ubuntu/kinetic ubuntu/noble ubuntu/oracular debian/jessie debian/stretch debian/buster debian/bullseye debian/bookworm debian/trixie'
debvers: 'ubuntu/bionic ubuntu/focal ubuntu/hirsute ubuntu/impish ubuntu/jammy ubuntu/kinetic ubuntu/noble debian/jessie debian/stretch debian/buster'
- uses: actions/upload-artifact@master
name: Upload AppImage (${{matrix.arch}})
@@ -319,14 +312,10 @@ jobs:
with:
fetch-depth: 0
- name: Code signing with Software Trust Manager
uses: digicert/ssm-code-signing@v1.0.0
if: github.event_name == 'push' && (startsWith(github.ref, 'refs/tags'))
- name: Installing Node
uses: actions/setup-node@v3.7.0
with:
node-version: 22
node-version: 18
- run: npm i -g npx
- run: rustup target add ${{matrix.rust_triple}}
@@ -346,48 +335,20 @@ jobs:
env:
ARCH: ${{matrix.arch}}
- name: Decode certificate
if: github.event_name == 'push' && (startsWith(github.ref, 'refs/tags'))
env:
SM_CLIENT_CERT_FILE_B64: ${{ secrets.SM_CLIENT_CERT_FILE_B64 }}
run: |
SM_CLIENT_CERT_FILE=$RUNNER_TEMP/certificate.p12
echo "$SM_CLIENT_CERT_FILE_B64" | base64 --decode > $SM_CLIENT_CERT_FILE
echo "SM_CLIENT_CERT_FILE=$SM_CLIENT_CERT_FILE" >> "$GITHUB_ENV"
shell: bash
- name: Build and sign packages
if: github.event_name == 'push' && (startsWith(github.ref, 'refs/tags'))
shell: powershell
run: |
Get-FileHash $env:SM_CLIENT_CERT_FILE -Algorithm MD5
smksp_registrar.exe list
smctl.exe healthcheck
smctl.exe keypair ls
smctl windows certsync --keypair-alias $env:SM_KEYPAIR_ALIAS
smctl.exe certificate ls
C:\Windows\System32\certutil.exe -csp "DigiCert Signing Manager KSP" -key -user
smksp_cert_sync.exe
# not used but necessary for electron-builder to run
$env:WIN_CSC_LINK=$env:SM_CLIENT_CERT_FILE
$env:WIN_CSC_KEY_PASSWORD=$env:SM_CLIENT_CERT_PASSWORD
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'))
env:
ARCH: ${{matrix.arch}}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
KEYGEN_TOKEN: ${{ secrets.KEYGEN_TOKEN }}
SM_CLIENT_CERT_PASSWORD: ${{ secrets.SM_CLIENT_CERT_PASSWORD }}
SM_PUBLISHER_NAME: ${{ secrets.SM_PUBLISHER_NAME }}
SM_API_KEY: ${{ secrets.SM_API_KEY }}
SM_HOST: ${{ vars.SM_HOST }}
SM_CODE_SIGNING_CERT_SHA1_HASH: ${{ vars.SM_CODE_SIGNING_CERT_SHA1_HASH }}
SM_KEYPAIR_ALIAS: ${{ vars.SM_KEYPAIR_ALIAS }}
# DEBUG: electron-builder,electron-builder:*
WIN_CSC_LINK: ${{ secrets.WIN_CSC_LINK }}
WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CSC_KEY_PASSWORD }}
DEBUG: electron-builder,electron-builder:*
- name: Build packages without signing
run: node scripts/build-windows.mjs
if: "! (github.event_name == 'push' && (startsWith(github.ref, 'refs/tags')))"
if: "! (github.repository == 'Eugeny/tabby' && github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags')))"
env:
ARCH: ${{matrix.arch}}

View File

@@ -18,7 +18,6 @@ jobs:
- name: Build
run: |
sudo apt update && sudo apt install libfontconfig1-dev
yarn cache clean
cd app
yarn

View File

@@ -1 +0,0 @@
https://null.page/funding.json

View File

@@ -66,7 +66,7 @@ Diese README ist auch verfügbar in: <a href="./README.md">:gb: English</a> ·
![](docs/readme-terminal.png)
* Ein VT220-Terminal + verschiedene Erweiterungen
* Ein V220-Terminal + verschiedene Erweiterungen
* Mehrere verschachtelte, geteilte Fenster
* Tabs auf jeder Seite des Fensters
* Optional andockbares Fenster mit einem globalen Spawn-Hotkey ("Quake-Konsole")
@@ -342,12 +342,6 @@ Dank geht an diese wunderbaren Menschen ([emoji key](https://allcontributors.org
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GeminiLn"><img src="https://avatars.githubusercontent.com/u/12425057?v=4?s=100" width="100px;" alt="Yu Qin"/><br /><sub><b>Yu Qin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=GeminiLn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fireblue"><img src="https://avatars.githubusercontent.com/u/1034929?v=4?s=100" width="100px;" alt="fireblue"/><br /><sub><b>fireblue</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=fireblue" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marko1616"><img src="https://avatars.githubusercontent.com/u/45327989?v=4?s=100" width="100px;" alt="marko1616"/><br /><sub><b>marko1616</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=marko1616" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.selfhosted.sg/"><img src="https://avatars.githubusercontent.com/u/128927132?v=4?s=100" width="100px;" alt="SelfHosted"/><br /><sub><b>SelfHosted</b></sub></a><br /><a href="#financial-SelfHosted-Club" title="Financial">💵</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://hiroga.hatenablog.com/"><img src="https://avatars.githubusercontent.com/u/13391129?v=4?s=100" width="100px;" alt="Hiroaki Ogasawara"/><br /><sub><b>Hiroaki Ogasawara</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=xhiroga" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ianaflous"><img src="https://avatars.githubusercontent.com/u/42301579?v=4?s=100" width="100px;" alt="ianaflous"/><br /><sub><b>ianaflous</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=ianaflous" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -67,7 +67,7 @@ Este fichero README está disponible en: <a href="./README.md">:gb: English</a>
![](docs/readme-terminal.png)
* Un terminal VT220 + varias extensiones
* Un terminal V220 + varias extensiones
* Múltiples paneles divididos anidados
* Pestañas en cualquier lado de la ventana
* Ventana acoplable opcional con una tecla de acceso directo global ("consola de Quake")
@@ -344,11 +344,6 @@ Gracias a estas maravillosas personas ([emoji key](https://allcontributors.org/d
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GeminiLn"><img src="https://avatars.githubusercontent.com/u/12425057?v=4?s=100" width="100px;" alt="Yu Qin"/><br /><sub><b>Yu Qin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=GeminiLn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fireblue"><img src="https://avatars.githubusercontent.com/u/1034929?v=4?s=100" width="100px;" alt="fireblue"/><br /><sub><b>fireblue</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=fireblue" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marko1616"><img src="https://avatars.githubusercontent.com/u/45327989?v=4?s=100" width="100px;" alt="marko1616"/><br /><sub><b>marko1616</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=marko1616" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.selfhosted.sg/"><img src="https://avatars.githubusercontent.com/u/128927132?v=4?s=100" width="100px;" alt="SelfHosted"/><br /><sub><b>SelfHosted</b></sub></a><br /><a href="#financial-SelfHosted-Club" title="Financial">💵</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://hiroga.hatenablog.com/"><img src="https://avatars.githubusercontent.com/u/13391129?v=4?s=100" width="100px;" alt="Hiroaki Ogasawara"/><br /><sub><b>Hiroaki Ogasawara</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=xhiroga" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -67,7 +67,7 @@ This README is also available in: <a href="./README.md">:gb: English</a> · <a
![](docs/readme-terminal.png)
* Terminal VT220 + berbagai macam ekstensi
* Terminal V220 + berbagai macam ekstensi
* Beberapa pembagian panel
* Tab di sisi mana pun dari jendela
* Jendela dockable opsional dengan hotkey spawn global ("Quake console")
@@ -341,12 +341,6 @@ Terima kasih kepada mereka yang telah membantu ([emoji key](https://allcontribut
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GeminiLn"><img src="https://avatars.githubusercontent.com/u/12425057?v=4?s=100" width="100px;" alt="Yu Qin"/><br /><sub><b>Yu Qin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=GeminiLn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fireblue"><img src="https://avatars.githubusercontent.com/u/1034929?v=4?s=100" width="100px;" alt="fireblue"/><br /><sub><b>fireblue</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=fireblue" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marko1616"><img src="https://avatars.githubusercontent.com/u/45327989?v=4?s=100" width="100px;" alt="marko1616"/><br /><sub><b>marko1616</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=marko1616" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.selfhosted.sg/"><img src="https://avatars.githubusercontent.com/u/128927132?v=4?s=100" width="100px;" alt="SelfHosted"/><br /><sub><b>SelfHosted</b></sub></a><br /><a href="#financial-SelfHosted-Club" title="Financial">💵</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://hiroga.hatenablog.com/"><img src="https://avatars.githubusercontent.com/u/13391129?v=4?s=100" width="100px;" alt="Hiroaki Ogasawara"/><br /><sub><b>Hiroaki Ogasawara</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=xhiroga" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ianaflous"><img src="https://avatars.githubusercontent.com/u/42301579?v=4?s=100" width="100px;" alt="ianaflous"/><br /><sub><b>ianaflous</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=ianaflous" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -68,7 +68,7 @@ Questo README è disponibile anche in: <a href="./README.md">:gb: English</a>
![](docs/readme-terminal.png)
* Un terminale VT220 + vari estensioni
* Un terminale V220 + vari estensioni
* Suddivisione in pannelli
* Schede su qualsiasi lato della finestra
* Finestra agganciabile opzionale con un tasto di scelta rapida ("Quake console")
@@ -337,11 +337,6 @@ Grazie a queste persone meravigliose ([emoji key](https://allcontributors.org/do
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GeminiLn"><img src="https://avatars.githubusercontent.com/u/12425057?v=4?s=100" width="100px;" alt="Yu Qin"/><br /><sub><b>Yu Qin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=GeminiLn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fireblue"><img src="https://avatars.githubusercontent.com/u/1034929?v=4?s=100" width="100px;" alt="fireblue"/><br /><sub><b>fireblue</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=fireblue" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marko1616"><img src="https://avatars.githubusercontent.com/u/45327989?v=4?s=100" width="100px;" alt="marko1616"/><br /><sub><b>marko1616</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=marko1616" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.selfhosted.sg/"><img src="https://avatars.githubusercontent.com/u/128927132?v=4?s=100" width="100px;" alt="SelfHosted"/><br /><sub><b>SelfHosted</b></sub></a><br /><a href="#financial-SelfHosted-Club" title="Financial">💵</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://hiroga.hatenablog.com/"><img src="https://avatars.githubusercontent.com/u/13391129?v=4?s=100" width="100px;" alt="Hiroaki Ogasawara"/><br /><sub><b>Hiroaki Ogasawara</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=xhiroga" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -74,7 +74,7 @@
![](docs/readme-terminal.png)
* VT220ターミナル各種拡張機能
* V220ターミナル各種拡張機能
* 複数ネストされたペイン分割に対応
* ウィンドウ内に自由に配置可能なタブ
* グローバルホットキーで呼び出せるドックウィンドウ機能("Quakeコンソール"
@@ -352,11 +352,6 @@ Windows上では、`Tabby.exe`がある場所と同じ場所に`data`フォル
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GeminiLn"><img src="https://avatars.githubusercontent.com/u/12425057?v=4?s=100" width="100px;" alt="Yu Qin"/><br /><sub><b>Yu Qin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=GeminiLn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fireblue"><img src="https://avatars.githubusercontent.com/u/1034929?v=4?s=100" width="100px;" alt="fireblue"/><br /><sub><b>fireblue</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=fireblue" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marko1616"><img src="https://avatars.githubusercontent.com/u/45327989?v=4?s=100" width="100px;" alt="marko1616"/><br /><sub><b>marko1616</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=marko1616" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.selfhosted.sg/"><img src="https://avatars.githubusercontent.com/u/128927132?v=4?s=100" width="100px;" alt="SelfHosted"/><br /><sub><b>SelfHosted</b></sub></a><br /><a href="#financial-SelfHosted-Club" title="Financial">💵</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://hiroga.hatenablog.com/"><img src="https://avatars.githubusercontent.com/u/13391129?v=4?s=100" width="100px;" alt="Hiroaki Ogasawara"/><br /><sub><b>Hiroaki Ogasawara</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=xhiroga" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -66,7 +66,7 @@ This README is also available in: <a href="./README.md">:gb: English</a> · <a
![](docs/readme-terminal.png)
* A VT220 터미널 + 다양한 확장
* A V220 터미널 + 다양한 확장
* 여러 개의 분할 창 중첩
* 모든 측면에 탭이 위치함
* 전역 스폰 단축키가 있는 도킹 가능한 윈도우 ("Quake console")
@@ -336,12 +336,6 @@ Pull requests and plugins are welcome!
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GeminiLn"><img src="https://avatars.githubusercontent.com/u/12425057?v=4?s=100" width="100px;" alt="Yu Qin"/><br /><sub><b>Yu Qin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=GeminiLn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fireblue"><img src="https://avatars.githubusercontent.com/u/1034929?v=4?s=100" width="100px;" alt="fireblue"/><br /><sub><b>fireblue</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=fireblue" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marko1616"><img src="https://avatars.githubusercontent.com/u/45327989?v=4?s=100" width="100px;" alt="marko1616"/><br /><sub><b>marko1616</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=marko1616" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.selfhosted.sg/"><img src="https://avatars.githubusercontent.com/u/128927132?v=4?s=100" width="100px;" alt="SelfHosted"/><br /><sub><b>SelfHosted</b></sub></a><br /><a href="#financial-SelfHosted-Club" title="Financial">💵</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://hiroga.hatenablog.com/"><img src="https://avatars.githubusercontent.com/u/13391129?v=4?s=100" width="100px;" alt="Hiroaki Ogasawara"/><br /><sub><b>Hiroaki Ogasawara</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=xhiroga" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ianaflous"><img src="https://avatars.githubusercontent.com/u/42301579?v=4?s=100" width="100px;" alt="ianaflous"/><br /><sub><b>ianaflous</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=ianaflous" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -72,7 +72,7 @@ This README is also available in: <a href="./README.es-ES.md">:es: Spanish</a>
![](docs/readme-terminal.png)
* A VT220 terminal + various extensions
* A V220 terminal + various extensions
* Multiple nested split panes
* Tabs on any side of the window
* Optional dockable window with a global spawn hotkey ("Quake console")
@@ -144,7 +144,7 @@ Plugins and themes can be installed directly from the Settings view inside Tabby
# Sponsors <!-- omit in toc -->
<a href="https://packagecloud.io"><img src="https://assets-production.packagecloud.io/assets/logo_v1-d5895e7b89b2dee19030e85515fd0f91d8f3b37c82d218a6531fc89c2b1b613c.png" width="200"></a>
[![](https://assets-production.packagecloud.io/assets/packagecloud-logo-light-scaled-26ce8e96060fddf74afbd4445e63ba35590d4aaa56edc98495bb390ef3cae0ae.png)](https://packagecloud.io)
[**packagecloud**](https://packagecloud.io) has provided free Debian/RPM repository hosting
@@ -152,7 +152,7 @@ Plugins and themes can be installed directly from the Settings view inside Tabby
[**keygen**](https://keygen.sh/?via=eugene) has provided free release & auto-update hosting
<a href="https://iqhive.com/"><img src="https://iqhive.com/img/icons/logo.svg" width="200"></a>
<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
@@ -360,12 +360,6 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GeminiLn"><img src="https://avatars.githubusercontent.com/u/12425057?v=4?s=100" width="100px;" alt="Yu Qin"/><br /><sub><b>Yu Qin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=GeminiLn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fireblue"><img src="https://avatars.githubusercontent.com/u/1034929?v=4?s=100" width="100px;" alt="fireblue"/><br /><sub><b>fireblue</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=fireblue" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marko1616"><img src="https://avatars.githubusercontent.com/u/45327989?v=4?s=100" width="100px;" alt="marko1616"/><br /><sub><b>marko1616</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=marko1616" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.selfhosted.sg/"><img src="https://avatars.githubusercontent.com/u/128927132?v=4?s=100" width="100px;" alt="SelfHosted"/><br /><sub><b>SelfHosted</b></sub></a><br /><a href="#financial-SelfHosted-Club" title="Financial">💵</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://hiroga.hatenablog.com/"><img src="https://avatars.githubusercontent.com/u/13391129?v=4?s=100" width="100px;" alt="Hiroaki Ogasawara"/><br /><sub><b>Hiroaki Ogasawara</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=xhiroga" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ianaflous"><img src="https://avatars.githubusercontent.com/u/42301579?v=4?s=100" width="100px;" alt="ianaflous"/><br /><sub><b>ianaflous</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=ianaflous" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -75,7 +75,7 @@ Ten plik README jest również dostępny w językach: <a href="./README.md">:gb
![](docs/readme-terminal.png)
* Konsola VT220 + wiele rozszerzeń
* Konsola V220 + wiele rozszerzeń
* Wiele nakładających się podzielonych okien
* Okna na każdej stronie ekranu
* Opcjonalne dokowanie okna za pomocą skrótu ("Quake console")

View File

@@ -67,7 +67,7 @@ Esse README também está disponível em: <a href="./README.md">:gb: English</a
![](docs/readme-terminal.png)
* Um terminal VT220 + várias extensões
* Um terminal V220 + várias extensões
* Múltiplos painéis divididos aninhados
* Guias em qualquer lado da janela
* Opção de minimizar para a barra de tarefas com uma tecla de atalho global ("Quake console")
@@ -345,11 +345,6 @@ Obrigado vai para essas pessoas maravilhosas ([emoji key](https://allcontributor
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GeminiLn"><img src="https://avatars.githubusercontent.com/u/12425057?v=4?s=100" width="100px;" alt="Yu Qin"/><br /><sub><b>Yu Qin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=GeminiLn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fireblue"><img src="https://avatars.githubusercontent.com/u/1034929?v=4?s=100" width="100px;" alt="fireblue"/><br /><sub><b>fireblue</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=fireblue" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marko1616"><img src="https://avatars.githubusercontent.com/u/45327989?v=4?s=100" width="100px;" alt="marko1616"/><br /><sub><b>marko1616</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=marko1616" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.selfhosted.sg/"><img src="https://avatars.githubusercontent.com/u/128927132?v=4?s=100" width="100px;" alt="SelfHosted"/><br /><sub><b>SelfHosted</b></sub></a><br /><a href="#financial-SelfHosted-Club" title="Financial">💵</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://hiroga.hatenablog.com/"><img src="https://avatars.githubusercontent.com/u/13391129?v=4?s=100" width="100px;" alt="Hiroaki Ogasawara"/><br /><sub><b>Hiroaki Ogasawara</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=xhiroga" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -67,7 +67,7 @@
![](docs/readme-terminal.png)
* Терминал VT220 + различные дополнения;
* Терминал V220 + различные дополнения;
* Деление окна на несколько панелей;
* Вкладки на любой стороне окна;
* Опционально закрепляемое окно с глобальной горячей клавишей для вызова («Quake console»);
@@ -337,12 +337,6 @@ Pull-запросы и плагины приветствуются!
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GeminiLn"><img src="https://avatars.githubusercontent.com/u/12425057?v=4?s=100" width="100px;" alt="Yu Qin"/><br /><sub><b>Yu Qin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=GeminiLn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fireblue"><img src="https://avatars.githubusercontent.com/u/1034929?v=4?s=100" width="100px;" alt="fireblue"/><br /><sub><b>fireblue</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=fireblue" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marko1616"><img src="https://avatars.githubusercontent.com/u/45327989?v=4?s=100" width="100px;" alt="marko1616"/><br /><sub><b>marko1616</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=marko1616" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.selfhosted.sg/"><img src="https://avatars.githubusercontent.com/u/128927132?v=4?s=100" width="100px;" alt="SelfHosted"/><br /><sub><b>SelfHosted</b></sub></a><br /><a href="#financial-SelfHosted-Club" title="Financial">💵</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://hiroga.hatenablog.com/"><img src="https://avatars.githubusercontent.com/u/13391129?v=4?s=100" width="100px;" alt="Hiroaki Ogasawara"/><br /><sub><b>Hiroaki Ogasawara</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=xhiroga" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ianaflous"><img src="https://avatars.githubusercontent.com/u/42301579?v=4?s=100" width="100px;" alt="ianaflous"/><br /><sub><b>ianaflous</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=ianaflous" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -66,7 +66,7 @@
![](docs/readme-terminal.png)
* 一个 VT220 终端 + 各种插件
* 一个 V220 终端 + 各种插件
* 多个嵌套的拆分窗格
* 可以将标签页设置在窗口的任意一侧
* 带有全局生成热键的可选可停靠窗口“Quake console”
@@ -336,12 +336,6 @@
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GeminiLn"><img src="https://avatars.githubusercontent.com/u/12425057?v=4?s=100" width="100px;" alt="Yu Qin"/><br /><sub><b>Yu Qin</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=GeminiLn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fireblue"><img src="https://avatars.githubusercontent.com/u/1034929?v=4?s=100" width="100px;" alt="fireblue"/><br /><sub><b>fireblue</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=fireblue" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/marko1616"><img src="https://avatars.githubusercontent.com/u/45327989?v=4?s=100" width="100px;" alt="marko1616"/><br /><sub><b>marko1616</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=marko1616" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.selfhosted.sg/"><img src="https://avatars.githubusercontent.com/u/128927132?v=4?s=100" width="100px;" alt="SelfHosted"/><br /><sub><b>SelfHosted</b></sub></a><br /><a href="#financial-SelfHosted-Club" title="Financial">💵</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://hiroga.hatenablog.com/"><img src="https://avatars.githubusercontent.com/u/13391129?v=4?s=100" width="100px;" alt="Hiroaki Ogasawara"/><br /><sub><b>Hiroaki Ogasawara</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=xhiroga" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/geodic"><img src="https://avatars.githubusercontent.com/u/64704703?v=4?s=100" width="100px;" alt="geodic"/><br /><sub><b>geodic</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=geodic" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ianaflous"><img src="https://avatars.githubusercontent.com/u/42301579?v=4?s=100" width="100px;" alt="ianaflous"/><br /><sub><b>ianaflous</b></sub></a><br /><a href="https://github.com/Eugeny/tabby/commits?author=ianaflous" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -1,7 +1,7 @@
import * as glasstron from 'glasstron'
import { autoUpdater } from 'electron-updater'
import { Subject, Observable, debounceTime } from 'rxjs'
import { BrowserWindow, app, ipcMain, Rectangle, Menu, screen, BrowserWindowConstructorOptions, TouchBar, nativeImage, WebContents, nativeTheme } from 'electron'
import { BrowserWindow, app, ipcMain, Rectangle, Menu, screen, BrowserWindowConstructorOptions, TouchBar, nativeImage, WebContents } from 'electron'
import ElectronConfig = require('electron-config')
import { enable as enableRemote } from '@electron/remote/main'
import * as os from 'os'
@@ -100,10 +100,6 @@ export class Window {
}
}
if (process.platform === 'darwin') {
bwOptions.visualEffectState = 'active'
}
if (process.platform === 'darwin') {
this.window = new BrowserWindow(bwOptions) as GlasstronWindow
} else {
@@ -112,15 +108,13 @@ export class Window {
this.webContents = this.window.webContents
this.window.webContents.once('did-finish-load', () => {
this.window.once('ready-to-show', () => {
if (process.platform === 'darwin') {
this.window.setVibrancy(macOSVibrancyType)
} else if (process.platform === 'win32' && this.configStore.appearance?.vibrancy) {
this.setVibrancy(true)
}
this.setDarkMode(this.configStore.appearance?.colorSchemeMode ?? 'dark')
if (!options.hidden) {
if (maximized) {
this.window.maximize()
@@ -145,7 +139,7 @@ export class Window {
enableRemote(this.window.webContents)
this.window.loadFile(path.join(app.getAppPath(), 'dist', 'index.html'))
this.window.loadURL(`file://${app.getAppPath()}/dist/index.html`, { extraHeaders: 'pragma: no-cache\n' })
this.window.webContents.setVisualZoomLevelLimits(1, 1)
this.window.webContents.setZoomFactor(1)
@@ -207,18 +201,6 @@ export class Window {
}
}
setDarkMode (mode: string): void {
if (process.platform === 'darwin') {
if ('light' === mode ) {
nativeTheme.themeSource = 'light'
} else if ('auto' === mode) {
nativeTheme.themeSource = 'system'
} else {
nativeTheme.themeSource = 'dark'
}
}
}
focus (): void {
this.window.focus()
}
@@ -391,10 +373,6 @@ export class Window {
this.setVibrancy(enabled, type)
})
this.on('window-set-dark-mode', (_, mode) => {
this.setDarkMode(mode)
})
this.on('window-set-window-controls-color', (_, theme) => {
if (process.platform === 'win32') {
const symbolColor: string = theme.foreground

View File

@@ -16,7 +16,7 @@
},
"dependencies": {
"@electron/remote": "^2",
"node-pty": "^1.0.0",
"node-pty": "^1.1.0-beta.14",
"any-promise": "^1.3.0",
"electron-config": "2.0.0",
"electron-debug": "^3.2.0",
@@ -30,19 +30,19 @@
"native-process-working-directory": "^1.0.2",
"npm": "6",
"rxjs": "^7.5.7",
"russh": "0.1.21",
"russh": "0.0.3",
"source-map-support": "^0.5.20",
"v8-compile-cache": "^2.3.0",
"yargs": "^17.7.2"
},
"optionalDependencies": {
"@tabby-gang/windows-blurbehind": "^3.1.0",
"@tabby-gang/windows-blurbehind": "^3.0.0",
"macos-native-processlist": "^2.1.0",
"patch-package": "^6.5.0",
"serialport": "11.0.1",
"serialport-binding-webserialapi": "^1.0.3",
"windows-native-registry": "^3.2.1",
"@tabby-gang/windows-process-tree": "^0.6.1"
"windows-process-tree": "^0.4.0"
},
"devDependencies": {
"@ngx-translate/core": "^14.0.0",
@@ -65,8 +65,6 @@
},
"resolutions": {
"*/node-abi": "^3",
"node-gyp": "^10.0.0",
"nan": "2.22.0",
"node-addon-api": "^8.3.0"
"node-gyp": "^10.0.0"
}
}

View File

@@ -1,53 +0,0 @@
diff --git a/node_modules/node-pty/binding.gyp b/node_modules/node-pty/binding.gyp
index 79a93e7..efb0a3f 100644
--- a/node_modules/node-pty/binding.gyp
+++ b/node_modules/node-pty/binding.gyp
@@ -18,6 +18,9 @@
]
}
},
+ 'defines': [
+ 'NOMINMAX'
+ ]
}],
],
},
diff --git a/node_modules/node-pty/src/win/winpty.cc b/node_modules/node-pty/src/win/winpty.cc
index b054dee..a094b1c 100644
--- a/node_modules/node-pty/src/win/winpty.cc
+++ b/node_modules/node-pty/src/win/winpty.cc
@@ -164,7 +164,7 @@ static NAN_METHOD(PtyStartProcess) {
Nan::ThrowError(why.str().c_str());
goto cleanup;
}
-
+ {
int cols = info[4]->Int32Value(Nan::GetCurrentContext()).FromJust();
int rows = info[5]->Int32Value(Nan::GetCurrentContext()).FromJust();
bool debug = Nan::To<bool>(info[6]).FromJust();
@@ -179,6 +179,7 @@ static NAN_METHOD(PtyStartProcess) {
throw_winpty_error("Error creating WinPTY config", error_ptr);
goto cleanup;
}
+ {
winpty_error_free(error_ptr);
// Set pty size on config
@@ -215,7 +216,7 @@ static NAN_METHOD(PtyStartProcess) {
winpty_error_free(error_ptr);
// Set return values
- v8::Local<v8::Object> marshal = Nan::New<v8::Object>();
+ {v8::Local<v8::Object> marshal = Nan::New<v8::Object>();
Nan::Set(marshal, Nan::New<v8::String>("innerPid").ToLocalChecked(), Nan::New<v8::Number>((int)GetProcessId(handle)));
Nan::Set(marshal, Nan::New<v8::String>("innerPidHandle").ToLocalChecked(), Nan::New<v8::Number>((int)handle));
Nan::Set(marshal, Nan::New<v8::String>("pid").ToLocalChecked(), Nan::New<v8::Number>((int)winpty_agent_process(pc)));
@@ -232,7 +233,7 @@ static NAN_METHOD(PtyStartProcess) {
Nan::Set(marshal, Nan::New<v8::String>("conout").ToLocalChecked(), Nan::New<v8::String>(conoutPipeNameStr).ToLocalChecked());
}
info.GetReturnValue().Set(marshal);
-
+ }}}
goto cleanup;
cleanup:

View File

@@ -178,20 +178,13 @@
dependencies:
debug "^4.3.2"
"@tabby-gang/windows-blurbehind@^3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@tabby-gang/windows-blurbehind/-/windows-blurbehind-3.1.0.tgz#bd1462f2a970d195e277d48cceb8b2c0a20f09bd"
integrity sha512-wTvyNrBDNxD4yq1bXv7lvXRQujJYRQ2Ke3LJyE9yzY9e/L9/fHVIuprIgAMiLsnW+BKPERq0k27iC38WnxUBZQ==
"@tabby-gang/windows-blurbehind@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@tabby-gang/windows-blurbehind/-/windows-blurbehind-3.0.0.tgz#48d409c2eb14a12c867b70de5ee4d6769ef45e8f"
integrity sha512-ah6eJcoQZWOZfu9sd2pWlOJmfl1v+2EZQMeIp7MWvg+/16WS16UFNdnOtlV6AUiABHfZo2QKfCNUEuorCM+Q2A==
dependencies:
"@types/node" "^10.12.18"
"@tabby-gang/windows-process-tree@^0.6.1":
version "0.6.1"
resolved "https://registry.yarnpkg.com/@tabby-gang/windows-process-tree/-/windows-process-tree-0.6.1.tgz#2a5a5cbbee16f611fc61d53cbadf930c8d3d20a8"
integrity sha512-I7AwncTXTmo1+WPfV+O+jYRJzjCMJznIjC/ycl4dP/n2HAocuXCIjTZfoMmL+rgjN2nRFpTyn6P+EhuIPMACbQ==
dependencies:
node-addon-api "7.1.0"
"@types/mz@2.7.4":
version "2.7.4"
resolved "https://registry.yarnpkg.com/@types/mz/-/mz-2.7.4.tgz#f9d1535cb5171199b28ae6abd6ec29e856551401"
@@ -2607,10 +2600,10 @@ mz@^2.7.0:
object-assign "^4.0.1"
thenify-all "^1.0.0"
nan@2.22.0, nan@^2.17.0:
version "2.22.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.22.0.tgz#31bc433fc33213c97bad36404bb68063de604de3"
integrity sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==
nan@^2.17.0:
version "2.20.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.20.0.tgz#08c5ea813dd54ed16e5bd6505bf42af4f7838ca3"
integrity sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==
napi-build-utils@^1.0.1:
version "1.0.2"
@@ -2655,10 +2648,25 @@ node-abi@^3.3.0:
dependencies:
semver "^7.3.5"
node-addon-api@3.1.0, node-addon-api@6.1.0, node-addon-api@7.1.0, node-addon-api@^3.0.2, node-addon-api@^3.1.0, node-addon-api@^4.0.0, node-addon-api@^4.3.0, node-addon-api@^8.3.0:
version "8.3.0"
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-8.3.0.tgz#ec3763f18befc1cdf66d11e157ce44d5eddc0603"
integrity sha512-8VOpLHFrOQlAH+qA0ZzuGRlALRA6/LVh8QJldbrC4DY0hXoMP0l4Acq8TzFC018HztWiRqyCEj2aTWY2UvnJUg==
node-addon-api@3.1.0, node-addon-api@^3.0.2, node-addon-api@^3.1.0:
version "3.1.0"
resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.1.0.tgz"
integrity sha512-flmrDNB06LIl5lywUz7YlNGZH/5p0M7W28k8hzd9Lshtdh1wshD2Y+U4h9LD6KObOy1f+fEVdgprPrEymjM5uw==
node-addon-api@6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-6.1.0.tgz#ac8470034e58e67d0c6f1204a18ae6995d9c0d76"
integrity sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==
node-addon-api@^4.0.0, node-addon-api@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f"
integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==
node-addon-api@^7.1.0:
version "7.1.1"
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558"
integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==
node-fetch-npm@^2.0.2:
version "2.0.4"
@@ -2690,12 +2698,12 @@ node-gyp@^10.0.0, node-gyp@^5.0.2, node-gyp@^5.1.0:
tar "^6.1.2"
which "^4.0.0"
node-pty@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.0.0.tgz#7daafc0aca1c4ca3de15c61330373af4af5861fd"
integrity sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA==
node-pty@^1.1.0-beta.14:
version "1.1.0-beta9"
resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.1.0-beta9.tgz#ed643cb3b398d031b4e31c216e8f3b0042435f1d"
integrity sha512-/Ue38pvXJdgRZ3+me1FgfglLd301GhJN0NStiotdt61tm43N5htUyR/IXOUzOKuNaFmCwIhy6nwb77Ky41LMbw==
dependencies:
nan "^2.17.0"
node-addon-api "^7.1.0"
nopt@^4.0.3:
version "4.0.3"
@@ -3628,10 +3636,10 @@ run-queue@^1.0.0, run-queue@^1.0.3:
dependencies:
aproba "^1.1.1"
russh@0.1.21:
version "0.1.21"
resolved "https://registry.yarnpkg.com/russh/-/russh-0.1.21.tgz#857b20c298a50a6657d1f1653ce9d149c68d6b5b"
integrity sha512-2zjOHTTDqaa3/pHUU+VCkoEqOXLpIpk9WATUaudtLGqy3n8Duz3WlhvyJzEmd+S+9eVGnQvyktpjtZziXLVHRA==
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"
@@ -4452,6 +4460,13 @@ windows-native-registry@^3.2.1:
dependencies:
node-addon-api "^3.1.0"
windows-process-tree@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/windows-process-tree/-/windows-process-tree-0.4.0.tgz#31ac49c5da557e628ce7e37a5800972173d3349a"
integrity sha512-9LunDnc1WwuhyLeTAXMFX8wbActGJtDCBaiapQXFYk/nO4W4X9YxOKV5g/lQL3XX69QYxveDbjVVrdnTt1qqCQ==
dependencies:
nan "^2.17.0"
worker-farm@^1.6.0, worker-farm@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8"

View File

@@ -8,6 +8,10 @@
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.device.microphone</key>
<true/>
<key>com.apple.security.device.camera</key>

View File

@@ -32,13 +32,11 @@ extraResources:
- extras
asarUnpack:
- 'dist/*.map'
- node_modules/keytar/build/Release/*.node
- node_modules/**/*.node
win:
icon: "./build/windows/icon.ico"
artifactName: tabby-${version}-portable-${env.ARCH}.${ext}
signtoolOptions:
rfc3161TimeStampServer: http://timestamp.sectigo.com
rfc3161TimeStampServer: http://timestamp.sectigo.com
nsis:
oneClick: false
artifactName: tabby-${version}-setup-${env.ARCH}.${ext}
@@ -72,8 +70,7 @@ linux:
executableArgs:
- "--no-sandbox"
desktop:
entry:
StartupWMClass: tabby
StartupWMClass: tabby
snap:
plugs:
- default
@@ -102,7 +99,3 @@ rpm:
- '_build_id_links none'
- '--replaces'
- 'terminus-terminal'
electronFuses:
runAsNode: false
enableNodeOptionsEnvironmentVariable: false
enableNodeCliInspectArguments: false

File diff suppressed because it is too large Load Diff

View File

@@ -1,64 +1,63 @@
@echo off
rem -- Copyright (c) 2012 Martin Ridgers
rem -- Portions Copyright (c) 2020-2024 Christopher Antos
rem -- License: http://opensource.org/licenses/MIT
:: Copyright (c) 2012 Martin Ridgers
:: License: http://opensource.org/licenses/MIT
@echo off
setlocal enableextensions
set clink_profile_arg=
set clink_quiet_arg=
rem -- Mimic cmd.exe's behaviour when starting from the start menu.
if /i "%~1"=="startmenu" (
:: Mimic cmd.exe's behaviour when starting from the start menu.
if /i "%1"=="startmenu" (
cd /d "%userprofile%"
shift
)
rem -- Check for the --profile option.
if /i "%~1"=="--profile" (
:: Check for the --profile option.
if /i "%1"=="--profile" (
set clink_profile_arg=--profile "%~2"
shift
shift
)
rem -- Check for the --quiet option.
if /i "%~1"=="--quiet" (
:: Check for the --quiet option.
if /i "%1"=="--quiet" (
set clink_quiet_arg= --quiet
shift
)
rem -- If the .bat is run without any arguments, then start a cmd.exe instance.
if _%1==_ (
:: If the .bat is run without any arguments, then start a cmd.exe instance.
if "%1"=="" (
call :launch
goto :end
)
rem -- Test for autorun.
if defined CLINK_NOAUTORUN if /i "%~1"=="inject" if /i "%~2"=="--autorun" goto :end
:: Test for autorun.
if defined CLINK_NOAUTORUN if /i "%1"=="inject" if /i "%2"=="--autorun" goto :end
rem -- Forward to appropriate loader, and endlocal before inject tags the prompt.
:: Endlocal before inject tags the prompt.
endlocal
:: Pass through to appropriate loader.
if /i "%processor_architecture%"=="x86" (
endlocal
"%~dp0\clink_x86.exe" %*
) else if /i "%processor_architecture%"=="arm64" (
endlocal
"%~dp0\clink_arm64.exe" %*
) else if /i "%processor_architecture%"=="amd64" (
if defined processor_architew6432 (
endlocal
"%~dp0\clink_x86.exe" %*
) else (
endlocal
"%~dp0\clink_x64.exe" %*
)
)
goto :end
:end
goto :eof
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:launch
setlocal enableextensions
setlocal
set WT_PROFILE_ID=
set WT_SESSION=
start "Clink" cmd.exe /s /k ""%~dpnx0" inject %clink_profile_arg%%clink_quiet_arg%"
endlocal
:end
exit /b 0

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -4,10 +4,10 @@
# Override the built-in Readline defaults with ones that provide a more
# enhanced Clink experience.
set colored-completion-prefix on
set colored-stats on
set mark-symlinked-directories on
set completion-auto-query-items on
set history-point-at-end-of-anchored-search on
set search-ignore-case on
colored-completion-prefix on
colored-stats on
mark-symlinked-directories on
completion-auto-query-items on
history-point-at-end-of-anchored-search on
search-ignore-case on

View File

@@ -1,32 +1,34 @@
# When this file is named "default_settings" and is in the binaries
# directory or profile directory, it provides enhanced default settings.
# Override built-in default settings with ones that provide a more
# enhanced Clink experience.
clink.default_bindings = windows
clink.autoupdate = off
cmd.ctrld_exits = False
color.arginfo = sgr 38;5;172
color.argmatcher = sgr 1;38;5;40
color.cmd = bold
color.cmdredir = sgr 38;5;172
color.cmdsep = sgr 38;5;135
color.comment_row = sgr 38;5;87;48;5;18
color.description = sgr 38;5;39
color.doskey = sgr 1;38;5;75
color.executable = sgr 1;38;5;33
color.filtered = bold
color.flag = sgr 38;5;117
color.hidden = sgr 38;5;160
color.histexpand = sgr 97;48;5;55
color.horizscroll = sgr 38;5;16;48;5;30
color.input = sgr 38;5;214
color.readonly = sgr 38;5;28
color.selected_completion = sgr 7
color.selection = sgr 38;5;16;48;5;179
color.unrecognized = sgr 38;5;203
history.max_lines = 25000
history.time_stamp = show
match.expand_envvars = True
match.substring = True
# When this file is named "default_settings" and is in the binaries
# directory or profile directory, it provides enhanced default settings.
# Override built-in default settings with ones that provide a more
# enhanced Clink experience.
autosuggest.enable = True
clink.default_bindings = windows
cmd.ctrld_exits = False
color.arginfo = sgr 38;5;172
color.argmatcher = sgr 1;38;5;40
color.cmd = sgr 1;38;5;231
color.cmdredir = sgr 38;5;172
color.cmdsep = sgr 38;5;214
color.comment_row = sgr 38;5;87;48;5;18
color.description = sgr 38;5;39
color.doskey = sgr 1;38;5;75
color.executable = sgr 1;38;5;33
color.filtered = sgr 38;5;231
color.flag = sgr 38;5;117
color.hidden = sgr 38;5;160
color.histexpand = sgr 97;48;5;55
color.horizscroll = sgr 38;5;16;48;5;30
color.input = sgr 38;5;222
color.readonly = sgr 38;5;28
color.selected_completion = sgr 38;5;16;48;5;254
color.selection = sgr 38;5;16;48;5;179
color.suggestion = sgr 38;5;239
color.unrecognized = sgr 38;5;203
history.max_lines = 25000
history.time_stamp = show
match.expand_envvars = True
match.substring = True

View File

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

View File

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

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: Czech\n"
"Language: cs_CZ\n"
"PO-Revision-Date: 2024-12-24 22:58\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: Danish\n"
"Language: da_DK\n"
"PO-Revision-Date: 2024-12-24 22:58\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"
@@ -211,7 +211,7 @@ msgstr "Sløring"
#: locale/tmp-html/tabby-terminal/src/components/appearanceSettingsTab.component.html:28
msgid "Bold font weight"
msgstr "Fed skriftvægt"
msgstr ""
#: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:132
#: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:80

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: Indonesian\n"
"Language: id_ID\n"
"PO-Revision-Date: 2025-01-22 22:02\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"
@@ -23,7 +23,7 @@ msgstr "{name} salin"
#: locale/tmp-html/tabby-terminal/src/components/appearanceSettingsTab.component.html:77
msgid "A second font family used to display characters missing in the main font"
msgstr "Keluarga huruf kedua digunakan untuk menampilkan karakter yang hilang di huruf utama"
msgstr "Keluarga font kedua digunakan untuk menampilkan karakter yang hilang di font utama"
#: tabby-core/src/components/transfersMenu.component.ts:49
msgid "Abort all"
@@ -100,12 +100,12 @@ msgstr "Izinkan buka dengan cepat terminal di direktori terpilih"
#: locale/tmp-html/tabby-core/src/components/welcomeTab.component.html:25
#: locale/tmp-html/tabby-terminal/src/components/colorSchemeSettingsTab.component.html:11
msgid "Always dark"
msgstr "Selalu gelap"
msgstr ""
#: locale/tmp-html/tabby-core/src/components/welcomeTab.component.html:27
#: locale/tmp-html/tabby-terminal/src/components/colorSchemeSettingsTab.component.html:13
msgid "Always light"
msgstr "Selalu terang"
msgstr ""
#: locale/tmp-html/tabby-terminal/src/components/appearanceSettingsTab.component.html:2
#: tabby-terminal/src/settings.ts:14
@@ -255,7 +255,7 @@ msgstr "Ubah baud rate"
#: tabby-core/src/tabContextMenu.ts:133
msgid "Change tab color"
msgstr "Rubah warna label"
msgstr ""
#: locale/tmp-html/tabby-settings/src/components/vaultSettingsTab.component.html:12
msgid "Change the master passphrase"
@@ -337,7 +337,7 @@ msgstr "Skema warna"
#: locale/tmp-html/tabby-terminal/src/components/colorSchemeSettingsTab.component.html:2
msgid "Color schemes"
msgstr "Warna Skema"
msgstr ""
#: locale/tmp-html/tabby-serial/src/components/serialProfileSettings.component.html:81
#: locale/tmp-html/tabby-ssh/src/components/sshProfileSettings.component.html:216
@@ -362,7 +362,7 @@ msgstr "Perintah-perintah"
#: tabby-core/src/theme.ts:16
msgid "Compact (legacy)"
msgstr "Padat (tua)"
msgstr ""
#: tabby-settings/src/components/configSyncSettingsTab.component.ts:126
msgid "Config deleted"
@@ -449,7 +449,7 @@ msgstr "Salin jalur saat ini"
#: tabby-electron/src/sftpContextMenu.ts:29
msgid "Copy full path"
msgstr "Salin alamat lengkap"
msgstr ""
#: locale/tmp-html/tabby-terminal/src/components/terminalSettingsTab.component.html:97
msgid "Copy on select"
@@ -1732,7 +1732,7 @@ msgstr "Ganti Nama"
#: tabby-core/src/hotkeys.ts:24
#: tabby-core/src/tabContextMenu.ts:121
msgid "Rename tab"
msgstr "Ganti nama label"
msgstr ""
#: locale/tmp-html/tabby-terminal/src/components/terminalSettingsTab.component.html:3
msgid "Rendering"
@@ -1770,7 +1770,7 @@ msgstr "Mulai ulang sesi SSH saat ini"
#: tabby-telnet/src/hotkeys.ts:10
msgid "Restart current Telnet session"
msgstr "Mulai ulang sesi Telnet saat ini"
msgstr ""
#: tabby-core/src/hotkeys.ts:64
msgid "Restart tab"
@@ -1782,7 +1782,7 @@ msgstr "Mulai ulang aplikasi untuk menerapkan perubahan"
#: tabby-settings/src/components/profilesSettingsTab.component.ts:316
msgid "Restore settings to defaults ?"
msgstr "Kembali ke settingan sebelumnya ?"
msgstr ""
#: tabby-settings/src/components/editProfileGroupModal.component.ts:36
msgid "Restore settings to inherited defaults ?"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: Italian\n"
"Language: it_IT\n"
"PO-Revision-Date: 2025-01-22 22:02\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"
@@ -23,7 +23,7 @@ msgstr "{name} copia"
#: locale/tmp-html/tabby-terminal/src/components/appearanceSettingsTab.component.html:77
msgid "A second font family used to display characters missing in the main font"
msgstr "Un set di caratteri secondario usato per mostrare quelli mancanti del font principale"
msgstr "Una famiglia di font secondaria usata per visualizzare quelle mancanti nella font principale"
#: tabby-core/src/components/transfersMenu.component.ts:49
msgid "Abort all"
@@ -192,7 +192,7 @@ msgstr "Modalità tasto backspace"
#: tabby-serial/src/components/serialTab.component.ts:93
#: tabby-serial/src/profiles.ts:88
msgid "Baud rate"
msgstr "Velocità trasmissione"
msgstr "Velocità di trasmissione"
#: tabby-terminal/src/hotkeys.ts:22
msgid "Beginning of the line"
@@ -479,7 +479,7 @@ msgstr "Crea cartella"
#: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:90
msgid "Current"
msgstr "Attuale"
msgstr "Corrente"
#: locale/tmp-html/tabby-terminal/src/components/colorSchemeSettingsForMode.component.html:3
msgid "Current color scheme"
@@ -487,7 +487,7 @@ msgstr "Tema in uso"
#: locale/tmp-html/tabby-ssh/src/components/hostKeyPromptModal.component.html:17
msgid "Current host key fingerprint"
msgstr "Firma chiave host attuale"
msgstr "Firma della chiave host attuale"
#: tabby-core/src/tabContextMenu.ts:184
msgid "Current process: {name}"
@@ -495,7 +495,7 @@ msgstr "Processo attuale: {name}"
#: locale/tmp-html/tabby-terminal/src/components/appearanceSettingsTab.component.html:51
msgid "Cursor shape"
msgstr "Forma cursore"
msgstr "Forma del cursore"
#: locale/tmp-html/tabby-terminal/src/components/colorSchemeSettingsForMode.component.html:46
msgid "Custom"
@@ -531,7 +531,7 @@ msgstr "\"Connetti a\" predefinito"
#: locale/tmp-html/tabby-settings/src/components/profilesSettingsTab.component.html:93
msgid "Default connection type used by quick connect feature (ex. SSH, Telnet)"
msgstr "Tipo di connessione predefinita usato dalla funzione di connessione rapida (es. SSH, Telnet)"
msgstr "Tipo di connessione predefinito usato dalla funzione di connessione rapida (es. SSH, Telnet)"
#: locale/tmp-html/tabby-settings/src/components/profilesSettingsTab.component.html:8
msgid "Default profile for new tabs"
@@ -791,7 +791,7 @@ msgstr "Cancella configurazione"
#: locale/tmp-html/tabby-settings/src/components/vaultSettingsTab.component.html:12
msgid "Erase the Vault"
msgstr "Cancella cassaforte"
msgstr "Cancella la cassaforte"
#: locale/tmp-html/tabby-plugin-manager/src/components/pluginsSettingsTab.component.html:6
msgid "Error in {plugin}:"
@@ -1189,7 +1189,7 @@ msgstr "Lingua"
#: locale/tmp-html/tabby-ssh/src/components/hostKeyPromptModal.component.html:11
msgid "Last known host key fingerprint"
msgstr "Ultima firma nota chiave host"
msgstr "Ultima firma nota della chiave host"
#: tabby-ssh/src/tabContextMenu.ts:32
msgid "Launch WinSCP"
@@ -1439,7 +1439,7 @@ msgstr "Nelle discussioni di GitHub"
#: locale/tmp-html/tabby-settings/src/components/editProfileModal.component.html:47
msgid "Only close the tab when session is explicitly terminated"
msgstr "Chiudi la scheda solo quando la sessione è esplicitamente terminata"
msgstr "Chiude la scheda solo quando la sessione è esplicitamente terminata"
#: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:46
msgid "Opacity"
@@ -1476,11 +1476,11 @@ msgstr "Arancione"
#: tabby-electron/src/shells/macDefault.ts:25
msgid "OS default"
msgstr "Predefinito S.O."
msgstr "Predefinito del OS"
#: tabby-electron/src/shells/winDefault.ts:43
msgid "OS default ({name})"
msgstr "Predefinito S.O. ({name})"
msgstr "Predefinito del OS ({name})"
#: tabby-terminal/src/components/streamProcessingSettings.component.ts:46
msgid "Output is shown as a hexdump"
@@ -1678,7 +1678,7 @@ msgstr "Connessione raw socket"
#: locale/tmp-html/tabby-ssh/src/components/sshProfileSettings.component.html:166
msgid "Ready Timeout (Milliseconds)"
msgstr "Tempo stato pronto (millisecondi)"
msgstr "Tempo di preparazione (in millisecondi)"
#: tabby-core/src/services/profiles.service.ts:235
#: tabby-core/src/services/profiles.service.ts:249
@@ -1694,7 +1694,7 @@ msgstr "Recente"
#: tabby-terminal/src/tabContextMenu.ts:115
#: tabby-terminal/src/tabContextMenu.ts:119
msgid "Reconnect"
msgstr "Riconnetti"
msgstr "Riconetti"
#: tabby-terminal/src/hotkeys.ts:102
msgid "Reconnect current tab (Serial/Telnet/SSH)"
@@ -1723,7 +1723,7 @@ msgstr "Remoto"
#: locale/tmp-html/tabby-terminal/src/components/terminalSettingsTab.component.html:124
msgid "Remove whitespace and newlines around the copied text"
msgstr "Rimuovi spazi bianchi e a ritorni a capo dal testo copiato"
msgstr "Rimuovi spazi bianchi e a riorni a capo dal testo copiato"
#: locale/tmp-html/tabby-settings/src/components/vaultSettingsTab.component.html:26
#: tabby-core/src/tabContextMenu.ts:120
@@ -2011,7 +2011,7 @@ msgstr "Visualizza predefiniti"
#: locale/tmp-html/tabby-terminal/src/components/terminalSettingsTab.component.html:146
msgid "Show Mixer"
msgstr "Visualizza mixer"
msgstr "Visualizza Mixer"
#: tabby-core/src/hotkeys.ts:56
msgid "Show pane labels (for rearranging)"
@@ -2177,7 +2177,7 @@ msgstr "Cambia l'implementazione del frontend del terminale (sperimentale)"
#: locale/tmp-html/tabby-settings/src/components/configSyncSettingsTab.component.html:4
msgid "Sync"
msgstr "Sincronizzazione"
msgstr "Sincronizazione"
#: locale/tmp-html/tabby-settings/src/components/configSyncSettingsTab.component.html:53
msgid "Sync automatically"
@@ -2382,7 +2382,7 @@ msgstr "Carica come nuova configurazione"
#: locale/tmp-html/tabby-terminal/src/components/terminalSettingsTab.component.html:39
msgid "Use {altKeyName} as the Meta key"
msgstr "Come chiave meta usa {altKeyName} "
msgstr "Usa {altKeyName} come chiave Meta"
#: locale/tmp-html/tabby-local/src/components/shellSettingsTab.component.html:5
msgid "Use ConPTY"
@@ -2554,7 +2554,7 @@ msgstr "Giallo"
#: locale/tmp-html/tabby-settings/src/components/setVaultPassphraseModal.component.html:4
msgid "You can change it later, but it's unrecoverable if forgotten."
msgstr "Si può modificare più tardi, ma è irrecuperabile se dimenticata."
msgstr "Si può cambiare più tardi, ma è irrecuperabile se dimenticata."
#: 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."

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: Japanese\n"
"Language: ja_JP\n"
"PO-Revision-Date: 2024-12-24 22:58\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"
@@ -185,7 +185,7 @@ msgstr "透過の種類"
#: locale/tmp-html/tabby-terminal/src/components/inputProcessingSettings.component.html:4
msgid "Backspace key mode"
msgstr "Backspaceキー"
msgstr "BackSpaceキー"
#: locale/tmp-html/tabby-serial/src/components/serialProfileSettings.component.html:14
#: tabby-serial/src/components/serialTab.component.ts:93
@@ -526,7 +526,7 @@ msgstr "垂直方向の分割サイズを縮小"
#: locale/tmp-html/tabby-settings/src/components/profilesSettingsTab.component.html:92
msgid "Default \"Connect to\" type"
msgstr "既定の接続方式"
msgstr "既定の接続"
#: locale/tmp-html/tabby-settings/src/components/profilesSettingsTab.component.html:93
msgid "Default connection type used by quick connect feature (ex. SSH, Telnet)"
@@ -630,7 +630,7 @@ msgstr "動的なタブ名を無効にする"
#: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:218
msgid "Disable fluent background while dragging"
msgstr "ドラッグ中はFluent背景を無効する"
msgstr "ドラッグ中はFluentを無効する"
#: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:204
msgid "Disable GPU acceleration"
@@ -757,7 +757,7 @@ msgstr "アップデートが利用可能になったら自動的にインスト
#: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:211
msgid "Enable fluent background option"
msgstr "Fluent背景オプションを有効にする"
msgstr "Fluent背景設定を有効にする"
#: locale/tmp-html/tabby-terminal/src/components/appearanceSettingsTab.component.html:16
msgid "Enable font ligatures"
@@ -806,7 +806,7 @@ msgstr "例:"
#: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:212
msgid "Experimental Windows 10 background style known to cause issues"
msgstr "不具合が報告されているWindows10風の実験的な背景"
msgstr "Windows10では問題が発生する場合があります。"
#: locale/tmp-html/tabby-settings/src/components/vaultSettingsTab.component.html:28
msgid "Export"
@@ -834,7 +834,7 @@ msgstr "固定"
#: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:219
msgid "Fluent background sometimes causes drag lag"
msgstr "Fluent背景はドラッグ中にラグを発生させることがあります"
msgstr "Fluentはドラッグ中にラグを発生させる場合があります"
#: tabby-terminal/src/tabContextMenu.ts:82
msgid "Focus all panes"
@@ -1052,7 +1052,7 @@ msgstr "ホットキー"
#: locale/tmp-html/tabby-settings/src/components/profilesSettingsTab.component.html:83
msgid "How Tabby presents itself through environment vars"
msgstr "環境変数を利用してTabbyを他の端末として認識させます"
msgstr "環境変数を利用してTabbyを他の端末のように扱わせます"
#: locale/tmp-html/tabby-ssh/src/components/sshProfileSettings.component.html:24
msgid "HTTP proxy"
@@ -1299,7 +1299,7 @@ msgstr "\"未分類\" に移動"
#: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:197
msgid "Moving the mouse over an inactive pane will cause it to activate"
msgstr "マウスの移動に合わせてアクティブなペインを切り替えます。"
msgstr "マウスの移動アクティブなペインを切り替えることができます。"
#: locale/tmp-html/tabby-settings/src/components/editProfileGroupModal.component.html:9
#: locale/tmp-html/tabby-settings/src/components/editProfileModal.component.html:12
@@ -1366,7 +1366,7 @@ msgstr "新しいタブ: {profile}"
#: tabby-local/src/buttonProvider.ts:20
#: tabby-local/src/tabContextMenu.ts:27
msgid "New terminal"
msgstr "新しい端末"
msgstr "新しいターミナル"
#: tabby-electron/src/hotkeys.ts:10
msgid "New window"
@@ -1956,7 +1956,7 @@ msgstr "Tabbyを %COMSPEC% に設定"
#: locale/tmp-html/tabby-settings/src/components/profilesSettingsTab.component.html:69
msgid "Set to 0 to disable recent profiles"
msgstr "セレクターに最近使用したプロファイルを表示したくない場合は0に設定してください"
msgstr "最近使用したプロファイルをセレクターに表示しない場合は0に設定してください"
#: locale/tmp-html/tabby-ssh/src/components/sshSettingsTab.component.html:36
msgid "Sets the SSH agent's named pipe path."
@@ -1994,7 +1994,7 @@ msgstr "複数行の貼り付けをする際に確認画面を表示します"
#: locale/tmp-html/tabby-settings/src/components/profilesSettingsTab.component.html:75
msgid "Show built-in profiles in selector"
msgstr "標準プロファイルをセレクターに表示"
msgstr "セレクターに標準プロファイルを表示する"
#: tabby-core/src/hotkeys.ts:12
msgid "Show command selector"
@@ -2089,7 +2089,7 @@ msgstr "ソースコード"
#: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:13
msgid "Spaciness"
msgstr "UI間隔"
msgstr "UI間隔"
#: tabby-core/src/tabContextMenu.ts:75
msgid "Split"

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: Russian\n"
"Language: ru_RU\n"
"PO-Revision-Date: 2024-12-24 22:58\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"
@@ -660,7 +660,7 @@ msgstr "Отключиться от {host}?"
#: locale/tmp-html/tabby-terminal/src/components/terminalSettingsTab.component.html:30
msgid "Display images via Sixel escape sequences"
msgstr "Отображать изображения при помощи управляющей последовательности Sixel"
msgstr "Отображать изображения при помощи сиксельной управляющей последовательности"
#: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:85
msgid "Display on"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: Serbian (Cyrillic)\n"
"Language: sr_SP\n"
"PO-Revision-Date: 2024-12-24 22:58\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"
@@ -342,7 +342,7 @@ msgstr ""
#: locale/tmp-html/tabby-serial/src/components/serialProfileSettings.component.html:81
#: locale/tmp-html/tabby-ssh/src/components/sshProfileSettings.component.html:216
msgid "Colors"
msgstr "Боје"
msgstr ""
#: tabby-core/src/hotkeys.ts:72
msgid "Combine all tabs into the current tab"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: Swedish\n"
"Language: sv_SE\n"
"PO-Revision-Date: 2024-12-24 22:58\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: Turkish\n"
"Language: tr_TR\n"
"PO-Revision-Date: 2025-01-22 22:02\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"
@@ -966,7 +966,7 @@ msgstr "Hazır bir GitHub sorunu oluşturun"
#: locale/tmp-html/tabby-plugin-manager/src/components/pluginsSettingsTab.component.html:25
msgid "Get"
msgstr "Yükle"
msgstr "Getir"
#: locale/tmp-html/tabby-settings/src/components/configSyncSettingsTab.component.html:18
msgid "Get it from the Tabby Web settings window"

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: Ukrainian\n"
"Language: uk_UA\n"
"PO-Revision-Date: 2024-12-24 22:58\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"
@@ -100,12 +100,12 @@ msgstr "Дозволяє швидко відкрити термінал у ви
#: locale/tmp-html/tabby-core/src/components/welcomeTab.component.html:25
#: locale/tmp-html/tabby-terminal/src/components/colorSchemeSettingsTab.component.html:11
msgid "Always dark"
msgstr "Завжди темна"
msgstr "Завжди темно"
#: locale/tmp-html/tabby-core/src/components/welcomeTab.component.html:27
#: locale/tmp-html/tabby-terminal/src/components/colorSchemeSettingsTab.component.html:13
msgid "Always light"
msgstr "Завжди світла"
msgstr "Завжди світити"
#: locale/tmp-html/tabby-terminal/src/components/appearanceSettingsTab.component.html:2
#: tabby-terminal/src/settings.ts:14

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: Chinese Simplified\n"
"Language: zh_CN\n"
"PO-Revision-Date: 2025-01-22 22:02\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"
@@ -43,7 +43,7 @@ msgstr "辅助功能"
#: locale/tmp-html/tabby-settings/src/components/windowSettingsTab.component.html:27
msgid "Acrylic background"
msgstr "毛玻璃背景"
msgstr "亚克力背景"
#: locale/tmp-html/tabby-local/src/components/commandLineEditor.component.html:24
#: locale/tmp-html/tabby-local/src/components/environmentEditor.component.html:11

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: tabby\n"
"Language-Team: Chinese Traditional\n"
"Language: zh_TW\n"
"PO-Revision-Date: 2025-01-22 22:02\n"
"PO-Revision-Date: 2024-07-10 09:04\n"
#: tabby-local/src/components/terminalTab.component.ts:113
msgid "\"{command}\" is still running. Close?"
@@ -1390,7 +1390,7 @@ msgstr "無色彩"
#: locale/tmp-html/tabby-terminal/src/components/terminalSettingsTab.component.html:85
msgid "No modifier"
msgstr "無修飾鍵"
msgstr "不可修改"
#: locale/tmp-html/tabby-serial/src/components/serialProfileSettings.component.html:41
msgid "None"

View File

@@ -39,11 +39,11 @@
"cross-env": "7.0.3",
"css-loader": "^6.7.3",
"deep-equal": "2.0.5",
"electron": "^32.2.7",
"electron-builder": "^26.0.0-alpha.10",
"electron": "^29",
"electron-builder": "^24.6.4",
"electron-download": "^4.1.1",
"electron-installer-snap": "^5.1.0",
"@electron/rebuild": "^3.7.1",
"electron-rebuild": "^3.2.9",
"eslint": "^8.48.0",
"eslint-import-resolver-typescript": "^3.6.0",
"eslint-plugin-import": "^2.28.1",
@@ -55,7 +55,7 @@
"lru-cache": "^6.0.0",
"macos-release": "^3.3.0",
"ngx-toastr": "^16.0.2",
"node-abi": "^3.71.0",
"node-abi": "^3.65.0",
"npmlog": "6.0.2",
"npx": "^10.2.2",
"patch-package": "^6.4.7",
@@ -96,8 +96,7 @@
"*/pug": "^3",
"lzma-native": "^8.0.6",
"**/graceful-fs": "^4.2.4",
"nan": "2.22.0",
"node-gyp": "^10.0.0"
"nan": "2.17.0"
},
"scripts": {
"build": "npm run build:typings && node scripts/build-modules.mjs",

View File

@@ -1,8 +1,8 @@
diff --git a/node_modules/app-builder-lib/out/appInfo.js b/node_modules/app-builder-lib/out/appInfo.js
index 7fbbef7..0821807 100644
index 49f6dca..0ea11f2 100644
--- a/node_modules/app-builder-lib/out/appInfo.js
+++ b/node_modules/app-builder-lib/out/appInfo.js
@@ -116,9 +116,7 @@ class AppInfo {
@@ -112,9 +112,7 @@ class AppInfo {
return this.info.metadata.name;
}
get linuxPackageName() {

View File

@@ -25,7 +25,7 @@ builder({
},
] : undefined,
},
publish: (process.env.KEYGEN_TOKEN && isTag) ? 'always' : 'never',
publish: process.env.KEYGEN_TOKEN ? isTag ? 'always' : 'onTagOrDraft' : 'never',
}).catch(e => {
console.error(e)
process.exit(1)

View File

@@ -28,7 +28,10 @@ builder({
},
mac: {
identity: !process.env.CI || process.env.CSC_LINK ? undefined : null,
notarize: !!process.env.APPLE_TEAM_ID,
notarize: process.env.APPLE_TEAM_ID ? {
appBundleId: 'org.tabby',
teamId: process.env.APPLE_TEAM_ID,
} : false,
},
npmRebuild: process.env.ARCH !== 'arm64',
publish: process.env.KEYGEN_TOKEN ? [
@@ -39,7 +42,7 @@ builder({
},
] : undefined,
},
publish: (process.env.KEYGEN_TOKEN && isTag) ? 'always' : 'never',
publish: process.env.KEYGEN_TOKEN ? isTag ? 'always' : 'onTagOrDraft' : 'never',
}).catch(e => {
console.error(e)
process.exit(1)

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env node
import { rebuild } from '@electron/rebuild'
import { rebuild } from 'electron-rebuild'
import * as path from 'path'
import * as vars from './vars.mjs'

View File

@@ -2,15 +2,11 @@
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
import { build as builder } from 'electron-builder'
import * as vars from './vars.mjs'
import { execSync } from 'child_process'
const isTag = (process.env.GITHUB_REF || process.env.BUILD_SOURCEBRANCH || '').startsWith('refs/tags/')
const keypair = process.env.SM_KEYPAIR_ALIAS
process.env.ARCH = process.env.ARCH || process.arch
console.log('Signing enabled:', !!keypair)
builder({
dir: true,
win: ['nsis', 'zip'],
@@ -26,35 +22,8 @@ builder({
channel: `latest-${process.env.ARCH}`,
},
] : undefined,
forceCodeSigning: !!keypair,
win: {
signtoolOptions: {
certificateSha1: process.env.SM_CODE_SIGNING_CERT_SHA1_HASH,
publisherName: process.env.SM_PUBLISHER_NAME,
signingHashAlgorithms: ['sha256'],
sign: keypair ? async function (configuration) {
console.log('Signing', configuration)
if (configuration.path) {
try {
const out = execSync(
`smctl sign --keypair-alias=${keypair} --input "${String(configuration.path)}"`
)
if (out.toString().includes('FAILED')) {
throw new Error(out.toString())
}
console.log(out.toString())
} catch (e) {
console.error(`Failed to sign ${configuration.path}`)
console.error(e)
process.exit(1)
}
}
} : undefined,
},
},
},
publish: (process.env.KEYGEN_TOKEN && isTag) ? 'always' : 'never',
publish: process.env.KEYGEN_TOKEN ? isTag ? 'always' : 'onTagOrDraft' : 'never',
}).catch(e => {
console.error(e)
process.exit(1)

View File

@@ -10,8 +10,6 @@ log.info('deps', 'app')
sh.cd('app')
sh.exec(`yarn install --force --network-timeout 1000000`, { fatal: true })
// Some native packages might fail to build before patch-package gets a chance to run via postinstall
sh.exec(`yarn postinstall`, { fatal: false })
sh.cd('..')
sh.cd('web')

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env node
import { rebuild } from '@electron/rebuild'
import { rebuild } from 'electron-rebuild'
import sh from 'shelljs'
import path from 'node:path'
import fs from 'node:fs'

View File

@@ -1,44 +0,0 @@
!
! Generated with :
! XRDB2Xreources.py
!
*.foreground: #c0caf5
*.background: #1a1b26
*.cursorColor: #c0caf5
!
! Black
*.color0: #15161e
*.color8: #414868
!
! Red
*.color1: #f7768e
*.color9: #f7768e
!
! Green
*.color2: #9ece6a
*.color10: #9ece6a
!
! Yellow
*.color3: #e0af68
*.color11: #e0af68
!
! Blue
*.color4: #7aa2f7
*.color12: #7aa2f7
!
! Magenta
*.color5: #bb9af7
*.color13: #bb9af7
!
! Cyan
*.color6: #7dcfff
*.color14: #7dcfff
!
! White
*.color7: #a9b1d6
*.color15: #c0caf5
!
! Bold, Italic, Underline
*.colorBD: #eeeeee
!*.colorIT:
!*.colorUL:

View File

@@ -1,44 +0,0 @@
!
! Generated with :
! XRDB2Xreources.py
!
*.foreground: #3760bf
*.background: #e1e2e7
*.cursorColor: #3760bf
!
! Black
*.color0: #e9e9ed
*.color8: #a1a6c5
!
! Red
*.color1: #f52a65
*.color9: #f52a65
!
! Green
*.color2: #587539
*.color10: #587539
!
! Yellow
*.color3: #8c6c3e
*.color11: #8c6c3e
!
! Blue
*.color4: #2e7de9
*.color12: #2e7de9
!
! Magenta
*.color5: #9854f1
*.color13: #9854f1
!
! Cyan
*.color6: #007197
*.color14: #007197
!
! White
*.color7: #6172b0
*.color15: #3760bf
!
! Bold, Italic, Underline
*.colorBD: #eeeeee
!*.colorIT:
!*.colorUL:

View File

@@ -1,44 +0,0 @@
!
! Generated with :
! XRDB2Xreources.py
!
*.foreground: #c0caf5
*.background: #24283b
*.cursorColor: #c0caf5
!
! Black
*.color0: #1d202f
*.color8: #414868
!
! Red
*.color1: #f7768e
*.color9: #f7768e
!
! Green
*.color2: #9ece6a
*.color10: #9ece6a
!
! Yellow
*.color3: #e0af68
*.color11: #e0af68
!
! Blue
*.color4: #7aa2f7
*.color12: #7aa2f7
!
! Magenta
*.color5: #bb9af7
*.color13: #bb9af7
!
! Cyan
*.color6: #7dcfff
*.color14: #7dcfff
!
! White
*.color7: #a9b1d6
*.color15: #c0caf5
!
! Bold, Italic, Underline
*.colorBD: #eeeeee
!*.colorIT:
!*.colorUL:

View File

@@ -31,8 +31,6 @@ hotkeys:
__nonStructural: true
profile-selectors:
__nonStructural: true
group-selectors:
__nonStructural: true
profiles: []
groups: []
profileDefaults:

View File

@@ -264,7 +264,6 @@ export class AppHotkeyProvider extends HotkeyProvider {
async provide (): Promise<HotkeyDescription[]> {
const profiles = await this.profilesService.getProfiles()
const groups = await this.profilesService.getProfileGroups()
return [
...this.hotkeys,
...profiles.map(profile => ({
@@ -275,10 +274,6 @@ export class AppHotkeyProvider extends HotkeyProvider {
id: `profile-selectors.${provider.id}`,
name: this.translate.instant('Show {type} profile selector', { type: provider.name }),
})),
...groups.map(group => ({
id: `group-selectors.${group.id}`,
name: this.translate.instant('Show profile selector for group {name}', { name: group.name }),
})),
]
}

View File

@@ -37,7 +37,7 @@ import { FastHtmlBindDirective } from './directives/fastHtmlBind.directive'
import { DropZoneDirective } from './directives/dropZone.directive'
import { CdkAutoDropGroup } from './directives/cdkAutoDropGroup.directive'
import { Theme, CLIHandler, TabContextMenuItemProvider, TabRecoveryProvider, HotkeyProvider, ConfigProvider, PlatformService, FileProvider, ProfilesService, ProfileProvider, QuickConnectProfileProvider, SelectorOption, Profile, SelectorService, CommandProvider, PartialProfileGroup, ProfileGroup } from './api'
import { Theme, CLIHandler, TabContextMenuItemProvider, TabRecoveryProvider, HotkeyProvider, ConfigProvider, PlatformService, FileProvider, ProfilesService, ProfileProvider, QuickConnectProfileProvider, SelectorOption, Profile, SelectorService, CommandProvider } from './api'
import { AppService } from './services/app.service'
import { ConfigService } from './services/config.service'
@@ -46,7 +46,7 @@ import { HotkeysService } from './services/hotkeys.service'
import { CustomMissingTranslationHandler, LocaleService } from './services/locale.service'
import { CommandService } from './services/commands.service'
import { NewTheme } from './theme'
import { StandardTheme, StandardCompactTheme, PaperTheme, NewTheme } from './theme'
import { CoreConfigProvider } from './config'
import { AppHotkeyProvider } from './hotkeys'
import { TaskCompletionContextMenu, CommonOptionsContextMenu, TabManagementContextMenu, ProfilesContextMenu } from './tabContextMenu'
@@ -60,6 +60,9 @@ export function TranslateMessageFormatCompilerFactory (): TranslateMessageFormat
const PROVIDERS = [
{ provide: HotkeyProvider, useClass: AppHotkeyProvider, multi: true },
{ provide: Theme, useClass: StandardTheme, multi: true },
{ provide: Theme, useClass: StandardCompactTheme, multi: true },
{ provide: Theme, useClass: PaperTheme, multi: true },
{ provide: Theme, useClass: NewTheme, multi: true },
{ provide: ConfigProvider, useClass: CoreConfigProvider, multi: true },
{ provide: TabContextMenuItemProvider, useClass: CommonOptionsContextMenu, multi: true },
@@ -178,24 +181,20 @@ export default class AppModule { // eslint-disable-line @typescript-eslint/no-ex
if (profile) {
profilesService.openNewTabForProfile(profile)
}
} else if (hotkey.startsWith('profile-selectors.')) {
}
if (hotkey.startsWith('profile-selectors.')) {
const id = hotkey.substring(hotkey.indexOf('.') + 1)
const provider = profilesService.getProviders().find(x => x.id === id)
if (!provider) {
return
}
this.showSelector(provider).catch(() => null)
} else if (hotkey.startsWith('group-selectors.')) {
const id = hotkey.substring(hotkey.indexOf('.') + 1)
const groups = await this.profilesService.getProfileGroups({ includeProfiles: true })
const group = groups.find(x => x.id === id)
if (!group) {
return
}
this.showGroupSelector(group).catch(() => null)
} else if (hotkey === 'command-selector') {
}
if (hotkey === 'command-selector') {
commands.showSelector().catch(() => null)
} else if (hotkey === 'profile-selector') {
}
if (hotkey === 'profile-selector') {
commands.run('core:profile-selector', {})
}
})
@@ -233,21 +232,6 @@ export default class AppModule { // eslint-disable-line @typescript-eslint/no-ex
await this.selector.show(this.translate.instant('Select profile'), options)
}
async showGroupSelector (group: PartialProfileGroup<ProfileGroup>): Promise<void> {
if (this.selector.active) {
return
}
const profiles = group.profiles ?? []
const options: SelectorOption<void>[] = profiles.map(p => ({
...this.profilesService.selectorOptionForProfile(p),
callback: () => this.profilesService.openNewTabForProfile(p),
}))
await this.selector.show(this.translate.instant('Select profile'), options)
}
static forRoot (): ModuleWithProviders<AppModule> {
return {
ngModule: AppModule,

View File

@@ -28,7 +28,7 @@ export class HomeBaseService {
}
openDiscord (): void {
this.platform.openExternal('https://discord.gg/Vn7BjmzhtF')
this.platform.openExternal('https://discord.gg/4c5EVTBhtp')
}
openTranslations (): void {

View File

@@ -487,12 +487,6 @@ export class ProfilesService {
delete profile.group
}
}
if (this.config.store.hotkeys['group-selectors'].hasOwnProperty(group.id)) {
const groupSelectorsHotkeys = { ...this.config.store.hotkeys['group-selectors'] }
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete groupSelectorsHotkeys[group.id]
this.config.store.hotkeys['group-selectors'] = groupSelectorsHotkeys
}
}
/**

View File

@@ -23,7 +23,6 @@ export class ThemesService {
) {
this.rootElementStyleBackup = document.documentElement.style.cssText
this.applyTheme(standardTheme)
this.applyThemeVariables()
config.ready$.toPromise().then(() => {
this.applyCurrentTheme()
this.applyThemeVariables()
@@ -38,11 +37,6 @@ export class ThemesService {
})
}
private getConfigStoreOrDefaults (): any {
/// Theme service is active before the vault is unlocked and config is available
return this.config.store ?? this.config.getDefaults()
}
private applyThemeVariables () {
if (!this.findCurrentTheme().followsColorScheme) {
document.documentElement.style.cssText = this.rootElementStyleBackup
@@ -66,7 +60,7 @@ export class ThemesService {
}
let background = Color(theme.background)
if (this.getConfigStoreOrDefaults().appearance.vibrancy) {
if (this.config.store?.appearance.vibrancy) {
background = background.fade(0.6)
}
// const background = theme.background
@@ -154,13 +148,13 @@ export class ThemesService {
vars['--bs-form-switch-bg'] = `url("data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%27-4 -4 8 8%27%3e%3ccircle r=%273%27 fill=%27${switchBackground}%27/%3e%3c/svg%3e")`
}
vars['--spaciness'] = this.getConfigStoreOrDefaults().appearance.spaciness
vars['--spaciness'] = this.config.store.appearance.spaciness
for (const [bg, fg] of contrastPairs) {
const colorBg = Color(vars[bg]).hsl()
const colorFg = Color(vars[fg]).hsl()
const bgContrast = colorBg.contrast(colorFg)
if (bgContrast < this.getConfigStoreOrDefaults().terminal.minimumContrastRatio) {
if (bgContrast < this.config.store.terminal.minimumContrastRatio) {
vars[fg] = this.ensureContrast(colorFg, colorBg).string()
}
}
@@ -169,7 +163,7 @@ export class ThemesService {
document.documentElement.style.setProperty(key, value)
}
document.body.classList.toggle('no-animations', !this.getConfigStoreOrDefaults().accessibility.animations)
document.body.classList.toggle('no-animations', !this.config.store.accessibility.animations)
}
private ensureContrast (color: Color, against: Color): Color {
@@ -184,7 +178,7 @@ export class ThemesService {
while (
(step < 1 && color.color[2] > 1 ||
step > 1 && color.color[2] < 99) &&
color.contrast(against) < this.getConfigStoreOrDefaults().terminal.minimumContrastRatio) {
color.contrast(against) < this.config.store.terminal.minimumContrastRatio) {
color.color[2] *= step
}
return color
@@ -195,22 +189,22 @@ export class ThemesService {
}
findCurrentTheme (): Theme {
return this.findTheme(this.getConfigStoreOrDefaults().appearance.theme) ?? this.standardTheme
return this.findTheme(this.config.store.appearance.theme) ?? this.standardTheme
}
/// @hidden
_getActiveColorScheme (): any {
let theme: PlatformTheme = 'dark'
if (this.getConfigStoreOrDefaults().appearance.colorSchemeMode === 'light') {
if (this.config.store.appearance.colorSchemeMode === 'light') {
theme = 'light'
} else if (this.getConfigStoreOrDefaults().appearance.colorSchemeMode === 'auto') {
} else if (this.config.store.appearance.colorSchemeMode === 'auto') {
theme = this.platform.getTheme()
}
if (theme === 'light') {
return this.getConfigStoreOrDefaults().terminal.lightColorScheme
return this.config.store.terminal.lightColorScheme
} else {
return this.getConfigStoreOrDefaults().terminal.colorScheme
return this.config.store.terminal.colorScheme
}
}
@@ -221,7 +215,7 @@ export class ThemesService {
document.querySelector('head')!.appendChild(this.styleElement)
}
this.styleElement.textContent = theme.css
document.querySelector('style#custom-css')!.innerHTML = this.getConfigStoreOrDefaults().appearance.css
document.querySelector('style#custom-css')!.innerHTML = this.config.store?.appearance?.css
this.themeChanged.next(theme)
}

View File

@@ -195,13 +195,7 @@ export class VaultService {
if (!vault) {
return null
}
let vaultSecret = vault.secrets.find(s => s.type === type && this.keyMatches(key, s))
if (!vaultSecret) {
// search for secret without host in vault (like a default user/password used in multiple servers)
key['host'] = null
vaultSecret = vault.secrets.find(s => s.type === type && this.keyMatches(key, s))
}
return vaultSecret ?? null
return vault.secrets.find(s => s.type === type && this.keyMatches(key, s)) ?? null
}
async addSecret (secret: VaultSecret): Promise<void> {

View File

@@ -0,0 +1,36 @@
@import './theme.scss';
app-root {
.tabs-on-side .tab-bar {
height: 100% !important;
}
.tab-bar {
height: 27px !important;
.btn-tab-bar {
line-height: 29px !important;
height: 27px !important;
align-items: center;
svg {
height: 14px;
}
}
.inset {
width: 70 !important;
}
}
terminaltab .content {
margin: 5px !important;
}
ssh-tab .content {
margin: 5px !important;
}
serial-tab .content {
margin: 5px !important;
}
}

View File

@@ -0,0 +1,407 @@
$black: #002b36;
$base02: #073642;
$base01: #586e75;
$base00: #657b83;
$base0: #839496;
$base1: #93a1a1;
$base2: #eee8d5;
$white: #fdf6e3;
$yellow: #b58900;
$orange: #cb4b16;
$red: #dc322f;
$pink: #d33682;
$purple: #6c71c4;
$blue: #268bd2;
$teal: #2aa198;
$green: #859900;
$tab-border-radius: 5px;
$button-hover-bg: rgba(0, 0, 0, .125);
$button-active-bg: rgba(0, 0, 0, .25);
$primary: #fd7e14;
$secondary: #495057;
$content-bg: rgba($white, 0.65);
$content-bg-solid: $white;
$body-bg: $base2;
$body-bg2: $base1;
$body-color: $black;
$font-family-sans-serif: "Source Sans Pro";
$font-size-base: 14rem / 16;
$btn-border-radius: 0;
$nav-tabs-border-width: 0;
$nav-tabs-border-radius: 0;
$nav-tabs-link-hover-border-color: $body-bg;
$nav-tabs-active-link-hover-color: $white;
$nav-tabs-active-link-hover-bg: $blue;
$nav-tabs-active-link-hover-border-color: darken($blue, 30%);
$nav-pills-border-radius: 0;
$input-bg: $base2;
$input-disabled-bg: $base1;
$input-color: $body-color;
$input-color-placeholder: $base1;
$input-border-color: $base1;
//$input-box-shadow: inset 0 1px 1px rgba($black,.075);
$input-border-radius: 0;
$custom-select-border-radius: 0;
$input-bg-focus: $input-bg;
//$input-border-focus: lighten($brand-primary, 25%);
//$input-box-shadow-focus: $input-box-shadow, rgba($input-border-focus, .6);
$input-color-focus: $input-color;
$input-group-addon-bg: $body-bg;
$input-group-addon-border-color: $input-border-color;
$modal-content-bg: $content-bg-solid;
$modal-content-border-color: $body-bg;
$modal-header-border-color: transparent;
$modal-footer-border-color: transparent;
$popover-bg: $body-bg;
$dropdown-bg: $body-bg;
$dropdown-link-color: $body-color;
$dropdown-link-hover-color: #333;
$dropdown-link-hover-bg: $body-bg2;
//$dropdown-link-active-color: $component-active-color;
//$dropdown-link-active-bg: $component-active-bg;
$dropdown-link-disabled-color: #333;
$dropdown-header-color: #333;
$list-group-color: $body-color;
$list-group-bg: rgba($black,.05);
$list-group-border-color: rgba($black,.1);
$list-group-hover-bg: rgba($black,.1);
$list-group-link-active-bg: rgba($black,.2);
$list-group-action-color: $body-color;
$list-group-action-bg: rgba($black,.05);
$list-group-action-active-bg: $list-group-link-active-bg;
$list-group-border-radius: 0;
$pre-bg: $dropdown-bg;
$pre-color: $dropdown-link-color;
$headings-font-weight: lighter;
$headings-color: $base0;
@import '~bootstrap/scss/bootstrap.scss';
window-controls {
svg {
transition: 0.25s fill;
fill: $base01;
}
button:hover {
background: rgba($black, 0.125);
svg {
fill: $black;
}
}
.btn-close:hover {
background: #8a2828;
}
}
$border-color: $base1;
app-root {
background: $body-bg;
&.vibrant {
background: rgba(255, 255, 255,.4) !important;
}
&> .content {
.tab-bar {
.btn-tab-bar {
background: transparent;
line-height: 42px;
align-items: center;
svg, path {
fill: $black;
fill-opacity: 0.75;
}
&:hover { background: rgba(0, 0, 0, .125) !important; }
&:active { background: rgba(0, 0, 0, .25) !important; }
}
&>.tabs {
tab-header {
border-left: 1px solid transparent;
border-right: 1px solid transparent;
color: $base01;
transition: 0.125s ease-out width;
.index {
color: rgba($black, 0.4);
}
button {
color: $body-color;
border: none;
transition: 0.25s all;
&:hover { background: $button-hover-bg !important; }
&:active { background: $button-active-bg !important; }
}
.progressbar {
background: $blue;
}
.activity-indicator {
background:rgba(0, 0, 0, 0.2);
}
&.active {
color: $black;
background: $content-bg;
border-left: 1px solid $border-color;
border-right: 1px solid $border-color;
}
}
}
}
&.tabs-on-top .tab-bar {
&>.background {
border-bottom: 1px solid $border-color;
}
tab-header {
border-bottom: 1px solid $border-color;
&.active {
border-bottom-color: transparent;
}
}
}
&:not(.tabs-on-top) .tab-bar {
&>.background {
border-top: 1px solid $border-color;
}
tab-header {
border-top: 1px solid $border-color;
&.active {
margin-top: -1px;
}
}
}
}
&.platform-win32, &.platform-linux {
border: 1px solid #111;
&>.content .tab-bar .tabs tab-header:first-child {
border-left: none;
}
}
}
tab-body {
background: $content-bg;
}
settings-tab > .content {
& > .nav {
background: rgba(0, 0, 0, 0.25);
border-right: 1px solid $body-bg;
& > .nav-item > .nav-link {
border: none;
padding: 10px 50px 10px 20px;
font-size: 14px;
&:not(.active) {
color: $body-color;
}
}
}
}
multi-hotkey-input {
.item {
background: $body-bg2;
border: 1px solid $blue;
border-radius: 3px;
margin-right: 5px;
.body {
padding: 3px 0 2px;
.stroke {
padding: 0 6px;
border-right: 1px solid $content-bg;
}
}
.remove {
padding: 3px 8px 2px;
}
}
.item:has(.duplicate) {
background-color: map-get($theme-colors, 'danger');
border: 1px solid map-get($theme-colors, 'danger');
}
.add {
color: #777;
padding: 4px 10px 0;
}
.add, .item .body, .item .remove {
&:hover { background: darken($body-bg2, 5%); }
&:active { background: darken($body-bg2, 15%); }
}
.add:has(.duplicate), .item:has(.duplicate) .body, .item:has(.duplicate) .remove {
&:hover { background: darken(map-get($theme-colors, 'danger'), 5%); }
&:active { background: darken(map-get($theme-colors, 'danger'), 15%); }
}
}
hotkey-input-modal {
.input {
background: $input-bg;
padding: 10px;
font-size: 24px;
line-height: 27px;
height: 55px;
.stroke {
background: $body-bg2;
border: 1px solid $blue;
border-radius: 3px;
margin-right: 10px;
padding: 3px 10px;
}
}
.timeout {
background: $input-bg;
div {
background: $blue;
}
}
}
.mb-3 label {
margin-bottom: 2px;
}
.nav-tabs {
.nav-link {
transition: 0.25s all;
border-bottom-color: $nav-tabs-border-color;
}
}
.btn-check:checked + label {
background: $blue;
}
.btn {
i + * {
margin-left: 5px;
}
&.btn-lg i + * {
margin-left: 10px;
}
}
.input-group-addon + .form-control {
border-left: none;
}
.input-group > select.form-control {
flex-direction: row;
}
.list-group-item {
transition: 0.25s background;
&:not(:first-child) {
border-top: none;
}
i + * {
margin-left: 10px;
}
}
select.form-control {
-webkit-appearance: none;
background-image: url("data:image/svg+xml;utf8,<svg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='24' height='24' viewBox='0 0 24 24'><path fill='#444' d='M7.406 7.828l4.594 4.594 4.594-4.594 1.406 1.406-6 6-6-6z'></path></svg>");
background-position: 100% 50%;
background-repeat: no-repeat;
padding-right: 30px;
}
checkbox i.on {
color: $blue;
}
toggle {
.body {
border-color: $base0 !important;
.toggle {
background: $base0 !important;
}
}
&.active .body .toggle {
background: map-get($theme-colors, primary) !important;
}
}
.list-group-item svg {
fill: $black;
}
.tabby-title {
color: $base01;
}
.tabby-logo {
filter: saturate(0);
}
start-page footer {
background: $white !important;
}
terminal-toolbar {
background: #ffffff4a !important;
border-bottom: 1px solid #00000026 !important;
}
.bg-dark{
background-color: $base2 !important;
}
split-tab-spanner {
background: rgba(0, 0, 0, .2);
&:hover, &.active {
background: rgba(255, 255, 255, .125);
}
}

428
tabby-core/src/theme.scss Normal file
View File

@@ -0,0 +1,428 @@
@import "./theme.vars";
// ---------
$button-hover-bg: rgba(0, 0, 0, .25);
$button-active-bg: rgba(0, 0, 0, .5);
@import '~bootstrap/scss/bootstrap.scss';
@import "./theme.vendor.scss";
window-controls {
svg {
transition: 0.25s fill;
fill: #aaa;
}
button:hover svg {
fill: white;
}
.btn-close:hover {
background: #8a2828;
}
}
$border-color: #111;
app-root {
background: $body-bg;
&.vibrant {
background: rgba(0,0,0,.65);
}
&> .content {
.tab-bar {
.btn-tab-bar {
background: transparent;
&:hover { background: rgba(0, 0, 0, .25) !important; }
&:active, &[aria-expanded-true] { background: rgba(0, 0, 0, .5) !important; }
&:focus {
box-shadow: none;
}
&::after {
display: none;
}
}
&>.tabs {
tab-header {
border-left: 1px solid transparent;
border-right: 1px solid transparent;
transition: 0.125s ease-out width;
.index {
color: rgba(255, 255, 255, 0.4);
}
.icon {
opacity: .75;
}
button {
color: $body-color;
border: none;
transition: 0.25s all;
right: 5px;
&:hover { background: $button-active-bg !important; }
&:active { background: $button-active-bg !important; }
}
.progressbar {
background: $green;
}
.activity-indicator {
background:rgba(255, 255, 255, 0.2);
}
&.active {
color: white;
background: $content-bg;
border-left: 1px solid $border-color;
border-right: 1px solid $border-color;
}
}
}
}
&.tabs-on-top .tab-bar {
&>.background {
border-bottom: 1px solid $border-color;
}
tab-header {
border-bottom: 1px solid $border-color;
&.active {
border-bottom-color: transparent;
}
}
}
&:not(.tabs-on-top) .tab-bar {
&>.background {
border-top: 1px solid $border-color;
}
tab-header {
border-top: 1px solid $border-color;
&.active {
margin-top: -1px;
}
}
}
}
&.platform-win32, &.platform-linux {
border: 1px solid #111;
&>.content {
margin: -1px; // expand the content into the border
.tab-bar .tabs tab-header:first-child {
border-left: none;
}
}
}
}
tab-body {
background: $content-bg;
terminal-toolbar .btn, .toolbar-pin-button {
font-weight: bold;
}
}
multi-hotkey-input {
.item {
background: $body-bg2;
border: 1px solid $blue;
border-radius: 3px;
margin-right: 5px;
.body {
padding: 3px 0 2px;
.stroke {
padding: 0 6px;
border-right: 1px solid $content-bg;
}
}
.remove {
padding: 3px 8px 2px;
}
}
.item:has(.duplicate) {
background-color: map-get($theme-colors, 'danger');
border: 1px solid map-get($theme-colors, 'danger');
}
.add {
color: #777;
padding: 4px 10px 0;
}
.add, .item .body, .item .remove {
&:hover { background: darken($body-bg2, 5%); }
&:active { background: darken($body-bg2, 15%); }
}
.add:has(.duplicate), .item:has(.duplicate) .body, .item:has(.duplicate) .remove {
&:hover { background: darken(map-get($theme-colors, 'danger'), 5%); }
&:active { background: darken(map-get($theme-colors, 'danger'), 15%); }
}
}
hotkey-input-modal {
.input {
background: $input-bg;
padding: 10px;
font-size: 24px;
line-height: 27px;
height: 55px;
.stroke {
background: $body-bg2;
border: 1px solid $blue;
border-radius: 3px;
margin-right: 10px;
padding: 3px 10px;
}
}
.timeout {
background: $input-bg;
div {
background: $blue;
}
}
}
.mb-3 label {
margin-bottom: 2px;
}
.btn-check:checked + label {
background: $blue;
}
.btn {
i + * {
margin-left: 5px;
}
&.btn-lg i + * {
margin-left: 10px;
}
}
.input-group-addon + .form-control {
border-left: none;
}
.input-group > select.form-control {
flex-direction: row;
}
.list-group-item {
// transition: 0.0625s background ease;
i + * {
margin-left: 10px;
}
}
.list-group.list-group-flush .list-group-item {
background: transparent;
border: none;
&:not(:last-child) {
border-bottom: none;
}
&.list-group-item-action {
&:hover, &.active {
background: $list-group-hover-bg;
}
}
}
.list-group-light {
.list-group-item {
border: none !important;
outline: none !important;
background: transparent;
border-radius: $border-radius;
margin: 0 !important;
&.list-group-item-action {
&:hover, &.active {
background: $component-active-bg;
color: $component-active-color;
}
}
}
}
checkbox i.on {
color: $blue;
}
.modal .modal-footer {
background: rgba(0, 0, 0, .25);
.btn {
font-weight: bold;
padding: 0.375rem 1.5rem;
}
}
.list-group-item svg {
fill: white;
fill-opacity: 0.75;
}
*::-webkit-scrollbar {
background: rgba(0, 0, 0, .125);
width: 10px;
margin: 5px;
}
*::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, .25);
}
*::-webkit-scrollbar-corner,
*::-webkit-resizer {
opacity: 0;
}
search-panel {
background: #131d27 !important;
input {
border-radius: 0 !important;
}
}
.btn {
cursor: pointer;
justify-content: flex-start;
overflow: hidden;
&.disabled,
&:disabled {
cursor: not-allowed;
}
}
.btn-warning:not(:disabled):not(.disabled) {
&.active, &:active {
color: $gray-900;
}
}
.btn-secondary:not(:disabled):not(.disabled) {
&.active, &:active {
background: #191e23;
align-items: center;
}
}
.btn-link {
text-decoration: none;
&:hover, &[aria-expanded=true], &:active, &.active {
color: $link-hover-color;
border-radius: $btn-border-radius;
}
&[aria-expanded=true], &:active, &.active {
background: rgba(255, 255, 255, 0.1);
}
}
.btn-group .btn.active {
border-color: transparent !important;
}
.nav-tabs {
margin-bottom: 10px;
&.nav-justified .nav-link {
margin-right: 5px;
}
.nav-link {
border: none;
border-bottom: $nav-tabs-border-width solid transparent;
text-transform: uppercase;
font-weight: bold;
padding: 5px 0;
margin-right: 20px;
uib-tab-heading > i {
font-size: 18px;
}
// @include hover-focus {
// color: $nav-tabs-link-active-color;
// }
&.disabled {
color: $nav-link-disabled-color;
border-color: transparent;
}
}
.nav-item:last-child .nav-link {
margin-right: 0;
}
.nav-link.active,
.nav-item.show .nav-link {
color: $nav-tabs-link-active-color;
border-color: $nav-tabs-link-active-border-color;
}
}
hr {
border-color: $list-group-border-color;
}
.dropdown-menu {
box-shadow: $dropdown-box-shadow;
}
ngx-colors-panel .opened {
background: $body-bg !important;
button {
color: $body-color !important;
}
.button svg {
fill: white;
}
}
split-tab-spanner {
background: rgba(0, 0, 0, .2);
&:hover, &.active {
background: rgba(255, 255, 255, .125);
}
}

View File

@@ -2,6 +2,32 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'
import { Injectable } from '@angular/core'
import { Theme } from './api'
/** @hidden */
@Injectable()
export class StandardTheme extends Theme {
name = _('Standard (legacy)')
css = require('./theme.scss')
terminalBackground = '#222a33'
}
/** @hidden */
@Injectable()
export class StandardCompactTheme extends Theme {
name = _('Compact (legacy)')
css = require('./theme.compact.scss')
terminalBackground = '#222a33'
macOSWindowButtonsInsetX = 8
macOSWindowButtonsInsetY = 6
}
/** @hidden */
@Injectable()
export class PaperTheme extends Theme {
name = _('Paper (legacy)')
css = require('./theme.paper.scss')
terminalBackground = '#f7f1e0'
}
/** @hidden */
@Injectable({ providedIn: 'root' })
export class NewTheme extends Theme {

View File

@@ -24,7 +24,6 @@
"devDependencies": {
"electron-promise-ipc": "^2.2.4",
"ps-node": "^0.1.6",
"ssh-config": "^5.0.0",
"tmp-promise": "^3.0.2",
"which": "^3.0.0",
"winston": "^3.3.3"

View File

@@ -1,6 +1,6 @@
import { NgModule } from '@angular/core'
import { PlatformService, LogService, UpdaterService, DockingService, HostAppService, ThemesService, Platform, AppService, ConfigService, WIN_BUILD_FLUENT_BG_SUPPORTED, isWindowsBuild, HostWindowService, HotkeyProvider, ConfigProvider, FileProvider } from 'tabby-core'
import { TerminalColorSchemeProvider, TerminalDecorator } from 'tabby-terminal'
import { TerminalColorSchemeProvider } from 'tabby-terminal'
import { SFTPContextMenuItemProvider, SSHProfileImporter, AutoPrivateKeyLocator } from 'tabby-ssh'
import { PTYInterface, ShellProvider, UACService } from 'tabby-local'
import { auditTime } from 'rxjs'
@@ -23,7 +23,6 @@ import { ElectronConfigProvider } from './config'
import { EditSFTPContextMenu } from './sftpContextMenu'
import { OpenSSHImporter, PrivateKeyLocator, StaticFileImporter } from './sshImporters'
import { ElectronPTYInterface } from './pty'
import { PathDropDecorator } from './pathDrop'
import { CmderShellProvider } from './shells/cmder'
import { Cygwin32ShellProvider } from './shells/cygwin32'
@@ -74,8 +73,6 @@ import { VSDevToolsProvider } from './shells/vs'
{ provide: PTYInterface, useClass: ElectronPTYInterface },
{ provide: TerminalDecorator, useClass: PathDropDecorator, multi: true },
// For WindowsDefaultShellProvider
PowerShellCoreShellProvider,
WSLShellProvider,
@@ -133,10 +130,7 @@ export default class ElectronModule {
})
})
config.changed$.subscribe(() => {
this.updateVibrancy()
this.updateDarkMode()
})
config.changed$.subscribe(() => this.updateVibrancy())
config.changed$.subscribe(() => this.updateWindowControlsColor())
@@ -179,11 +173,6 @@ export default class ElectronModule {
this.hostWindow.setOpacity(this.config.store.appearance.opacity)
}
private updateDarkMode () {
const colorSchemeMode = this.config.store.appearance.colorSchemeMode
this.electron.ipcRenderer.send('window-set-dark-mode', colorSchemeMode)
}
private updateWindowControlsColor () {
// if windows and not using native frame, WCO does not exist, return.
if (this.hostApp.platform === Platform.Windows && this.config.store.appearance.frame === 'native') {

View File

@@ -10,7 +10,7 @@ try {
} catch { }
try {
var windowsProcessTree = require('@tabby-gang/windows-process-tree') // eslint-disable-line @typescript-eslint/no-var-requires, no-var
var windowsProcessTree = require('windows-process-tree') // eslint-disable-line @typescript-eslint/no-var-requires, no-var
} catch { }
export class ElectronPTYInterface extends PTYInterface {

View File

@@ -18,7 +18,7 @@ const fontManager = require('fontmanager-redux') // eslint-disable-line
try {
// eslint-disable-next-line no-var
var windowsProcessTreeNative = require('@tabby-gang/windows-process-tree/build/Release/windows_process_tree.node')
var windowsProcessTreeNative = require('windows-process-tree/build/Release/windows_process_tree.node')
// eslint-disable-next-line no-var
var wnr = require('windows-native-registry')
} catch { }

View File

@@ -1,346 +1,130 @@
import at from 'core-js-pure/actual/array/at'
import * as fs from 'fs/promises'
import * as fsSync from 'fs'
import * as path from 'path'
import * as glob from 'glob'
import slugify from 'slugify'
import * as yaml from 'js-yaml'
import { Injectable } from '@angular/core'
import { PartialProfile } from 'tabby-core'
import {
SSHProfileImporter,
PortForwardType,
SSHProfile,
AutoPrivateKeyLocator,
ForwardedPortConfig,
} from 'tabby-ssh'
import { SSHProfileImporter, PortForwardType, SSHProfile, SSHProfileOptions, AutoPrivateKeyLocator } from 'tabby-ssh'
import { ElectronService } from './services/electron.service'
import SSHConfig, { Directive, LineType } from 'ssh-config'
// Enum to delineate the properties in SSHProfile options
enum SSHProfilePropertyNames {
Host = 'host',
Port = 'port',
User = 'user',
X11 = 'x11',
PrivateKeys = 'privateKeys',
KeepaliveInterval = 'keepaliveInterval',
KeepaliveCountMax = 'keepaliveCountMax',
ReadyTimeout = 'readyTimeout',
JumpHost = 'jumpHost',
AgentForward = 'agentForward',
ProxyCommand = 'proxyCommand',
ForwardedPorts = 'forwardedPorts',
}
// Data structure to map the (lowercase) ssh-config attributes (as keys) to a tuple
// containing the name of the corresponding SSHProfile attribute
const decodeFields: Record<string, SSHProfilePropertyNames> = {
hostname: SSHProfilePropertyNames.Host,
user: SSHProfilePropertyNames.User,
port: SSHProfilePropertyNames.Port,
forwardx11: SSHProfilePropertyNames.X11,
serveraliveinterval: SSHProfilePropertyNames.KeepaliveInterval,
serveralivecountmax: SSHProfilePropertyNames.KeepaliveCountMax,
connecttimeout: SSHProfilePropertyNames.ReadyTimeout,
proxyjump: SSHProfilePropertyNames.JumpHost,
forwardagent: SSHProfilePropertyNames.AgentForward,
identityfile: SSHProfilePropertyNames.PrivateKeys,
proxycommand: SSHProfilePropertyNames.ProxyCommand,
localforward: SSHProfilePropertyNames.ForwardedPorts,
remoteforward: SSHProfilePropertyNames.ForwardedPorts,
dynamicforward: SSHProfilePropertyNames.ForwardedPorts,
}
// Function to use the above to return details corresponding to the supplied SSHProperty name.
// If the name of the supplied SSH Config file Property is valid, and one that we process,
// then we get back the name of the corresponding Property in the SSHProfile object
function decodeTarget (SSHProperty: string): string {
const lower = SSHProperty.toLowerCase()
if (lower in decodeFields) {
return decodeFields[lower]
}
return ''
}
// Function to combine SSHConfig values into a single string. This is used to smash
// together the proxyCommand values which are split on whitespace and presented as
// an array of objects in the SSHConfig object
function convertSSHConfigValuesToString (arg: string | string[] | object[]): string {
if (typeof arg === 'string') { return arg }
let allStrings = true
for (const item of arg) {
if (typeof item !== 'string') {
allStrings = false
break
}
}
if (allStrings) {
return arg.join(' ')
}
// Have to explicitly unwrap the arg into a list of objects to avoid Typescript grumbles
const objList: object[] = []
for (const item of arg) {
if ( typeof item === 'object' && 'val' in item ) {
objList.push(item)
}
}
return objList.filter(obj => 'val' in obj)
.map(obj => 'val' in obj ? obj.val as string: '')
.join(' ')
}
// Function to read in the SSH config file recursively and parse any Include directives
async function parseSSHConfigFile (
filePath: string,
visited = new Set<string>(),
): Promise<SSHConfig> {
// If we've already processed this file, return an empty config to avoid infinite recursion
if (visited.has(filePath)) {
return SSHConfig.parse('')
}
visited.add(filePath)
let raw = ''
try {
raw = await fs.readFile(filePath, 'utf8')
} catch (err) {
console.error(`Error reading SSH config file: ${filePath}`, err)
return SSHConfig.parse('')
}
const parsed = SSHConfig.parse(raw)
const merged = SSHConfig.parse('')
for (const entry of parsed) {
if (entry.type === LineType.DIRECTIVE && entry.param.toLowerCase() === 'include') {
const directive = entry as Directive
if (typeof directive.value !== 'string') {
continue
}
// ssh_config(5) says "Files without absolute paths are assumed to be in ~/.ssh if included in a user configuration file or /etc/ssh if included from the system configuration file."
let incPath = ''
if (path.isAbsolute(directive.value)) {
incPath = directive.value
} else if (directive.value.startsWith('~')) {
incPath = path.join(process.env.HOME ?? '~', directive.value.slice(1))
} else {
incPath = path.join(process.env.HOME ?? '~', '.ssh', directive.value)
}
const matches = glob.sync(incPath)
for (const match of matches) {
const stat = await fs.stat(match)
if (stat.isDirectory()) {
continue
}
const matchedConfig = await parseSSHConfigFile(match, visited)
merged.push(...matchedConfig)
}
} else {
merged.push(entry)
}
}
return merged
}
// Function to take an ssh-config entry and convert it into an SSHProfile
function convertHostToSSHProfile (host: string, settings: Record<string, string | string[] | object[] >): PartialProfile<SSHProfile> {
// inline function to generate an id for this profile
const deriveID = (name: string) => 'openssh-config:' + slugify(name)
// Start point of the profile, with an ID, name, type and group
const thisProfile: PartialProfile<SSHProfile> = {
id: deriveID(host),
name: `${host} (.ssh/config)`,
type: 'ssh',
group: 'Imported from .ssh/config',
}
const options = {}
function convertToForwardedPortDescriptor (forwardType: PortForwardType.Local | PortForwardType.Remote | PortForwardType.Dynamic, details: string): ForwardedPortConfig {
const detailsParts = details.split(/\s/)
const bindParts = detailsParts[0].trim().split(':')
if (bindParts.length === 1) {
bindParts.unshift('127.0.0.1')
}
let tgtParts = ['', '22']
if ( detailsParts.length > 1 ) {
tgtParts = detailsParts[1].trim().split(':')
}
return {
host: bindParts[0],
port: parseInt(bindParts[1]),
targetAddress: tgtParts[0],
targetPort: parseInt(tgtParts[1]),
type: forwardType,
description: details,
}
}
// for each ssh-config key in turn...
for (const key in settings) {
// decode a target attribute and an action
const targetName = decodeTarget(key)
switch (targetName) {
// The following have single string values
case SSHProfilePropertyNames.User:
case SSHProfilePropertyNames.Host:
case SSHProfilePropertyNames.JumpHost:
const basicString = settings[key]
if (typeof basicString === 'string') {
if (targetName === SSHProfilePropertyNames.JumpHost) {
options[targetName] = deriveID(basicString)
} else {
options[targetName] = basicString
}
} else {
console.log('Unexpected value in settings for ' + key)
}
break
// The following have single integer values
case SSHProfilePropertyNames.Port:
case SSHProfilePropertyNames.KeepaliveInterval:
case SSHProfilePropertyNames.KeepaliveCountMax:
case SSHProfilePropertyNames.ReadyTimeout:
const numberString = settings[key]
if (typeof numberString === 'string') {
options[targetName] = parseInt(numberString, 10)
} else {
console.log('Unexpected value in settings for ' + key)
}
break
// The following have single yes/no values
case SSHProfilePropertyNames.X11:
case SSHProfilePropertyNames.AgentForward:
let booleanString = settings[key]
booleanString = typeof booleanString === 'string' ? booleanString.toLowerCase() : ''
if ( booleanString === 'yes' || booleanString === 'no' ) {
options[targetName] = booleanString === 'yes'
} else {
console.log('Unexpected value in settings for ' + key)
}
break
// ProxyCommand will be an array if unquoted and containing multiple words,
// or a simple string otherwise
case SSHProfilePropertyNames.ProxyCommand:
const proxyCommand = convertSSHConfigValuesToString(settings[key])
options[targetName] = proxyCommand
break
// IdentityFile may have multiple values and the need to have '~' converted to the
// path to the HOME directory
case SSHProfilePropertyNames.PrivateKeys:
const processedKeys: string [] = (settings[key] as string[]).map( s => {
let retVal: string = s
if (s.startsWith('~/')) {
retVal = path.join(process.env.HOME ?? '~', s.slice(2))
}
return retVal
})
options[targetName] = processedKeys
break
// The port forwarding directives all end up in the same space, but with a different value
// in the SSHProfileOptions object
case SSHProfilePropertyNames.ForwardedPorts:
const forwardTypeString = key.toLowerCase()
let forwardType: PortForwardType | null = null
switch (forwardTypeString) {
case 'localforward':
forwardType = PortForwardType.Local
break
case 'remoteforward':
forwardType = PortForwardType.Remote
break
case 'dynamicforward':
forwardType = PortForwardType.Dynamic
break
}
if (forwardType) {
options[targetName] ??= []
for (const forwarderDetails of settings[key]) {
if (typeof forwarderDetails === 'string') {
options[targetName].push(convertToForwardedPortDescriptor(forwardType, forwarderDetails))
}
}
}
break
}
}
thisProfile.options = options
return thisProfile
}
function convertToSSHProfiles (config: SSHConfig): PartialProfile<SSHProfile>[] {
const myMap = new Map<string, PartialProfile<SSHProfile>>()
function noWildCardsInName (name: string) {
return !/[?*]/.test(name)
}
for (const entry of config) {
// Each entry represents a line in the SSH Config. If the line is a 'Host' line,
// then it will also contain the configuration for that identifiable Host.
// There may be more than one host per line and some 'Hosts' have wildcards in their
// names
// If this is a genuine entry rather than a Comment...
// ... and there is a 'Host' Parameter
if (entry.type === LineType.DIRECTIVE && entry.param === 'Host') {
// for each Name in this entry
const hostList: string[] = []
// if there is more than one host specified on this line, then the names will be
// in an array
if (typeof entry.value === 'string') {
hostList.push(entry.value)
} else if (Array.isArray(entry.value)) {
for (const item of entry.value) {
hostList.push(item.val)
}
}
// for each Host identified on this line, check that there are no wildcards in the
// name and that we've not seen the name before.
// If that is the case, then get the full configuration for this name.
// If that has a 'Hostname' property (if that's missing, the name is not usable
// for our purposes) then convert the configuration into an SSHProfile and stash it
for (const host of hostList) {
if (noWildCardsInName(host)) {
if (!(host in myMap)) {
// NOTE: SSHConfig.compute() lies about the return types
const configuration: Record<string, string | string[] | object[]> = config.compute(host)
if (Object.keys(configuration).map(key => key.toLowerCase()).includes('hostname')) {
myMap[host] = convertHostToSSHProfile(host, configuration)
}
}
}
}
}
}
// Convert the values from the map into a list of Partial SSHProfiles sorted
// by Hostname
return Object.keys(myMap).sort().map(key => myMap[key])
}
@Injectable({ providedIn: 'root' })
export class OpenSSHImporter extends SSHProfileImporter {
async getProfiles (): Promise<PartialProfile<SSHProfile>[]> {
const deriveID = name => 'openssh-config:' + slugify(name)
const results: PartialProfile<SSHProfile>[] = []
const configPath = path.join(process.env.HOME ?? '~', '.ssh', 'config')
try {
const config: SSHConfig = await parseSSHConfigFile(configPath)
return convertToSSHProfiles(config)
const lines = (await fs.readFile(configPath, 'utf8')).split('\n')
const globalOptions: Partial<SSHProfileOptions> = {}
let currentProfile: PartialProfile<SSHProfile>|null = null
for (let line of lines) {
if (line.trim().startsWith('#') || !line.trim()) {
continue
}
if (line.toLowerCase().startsWith('host ')) {
if (currentProfile) {
results.push(currentProfile)
}
const name = line.substr(5).trim()
currentProfile = {
id: deriveID(name),
name: `${name} (.ssh/config)`,
type: 'ssh',
group: 'Imported from .ssh/config',
options: {
...globalOptions,
host: name,
},
}
} else {
const target: Partial<SSHProfileOptions> = currentProfile?.options ?? globalOptions
line = line.trim()
const idx = /\s/.exec(line)?.index ?? -1
if (idx === -1) {
continue
}
const key = line.substr(0, idx).trim()
const value = line.substr(idx + 1).trim()
if (key === 'IdentityFile') {
target.privateKeys = value.split(',').map(s => s.trim()).map(s => {
if (s.startsWith('~')) {
s = path.join(process.env.HOME ?? '~', s.slice(2))
}
return s
})
} else if (key === 'RemoteForward') {
const bind = value.split(/\s/)[0].trim()
const tgt = value.split(/\s/)[1].trim()
target.forwardedPorts ??= []
target.forwardedPorts.push({
type: PortForwardType.Remote,
description: value,
host: bind.split(':')[0] ?? '127.0.0.1',
port: parseInt(bind.split(':')[1] ?? bind),
targetAddress: tgt.split(':')[0],
targetPort: parseInt(tgt.split(':')[1]),
})
} else if (key === 'LocalForward') {
const bind = value.split(/\s/)[0].trim()
const tgt = value.split(/\s/)[1].trim()
target.forwardedPorts ??= []
target.forwardedPorts.push({
type: PortForwardType.Local,
description: value,
host: bind.includes(':') ? bind.split(':')[0] : '127.0.0.1',
port: parseInt(at(bind.split(':'), -1)),
targetAddress: tgt.split(':')[0],
targetPort: parseInt(tgt.split(':')[1]),
})
} else if (key === 'DynamicForward') {
const bind = value.trim()
target.forwardedPorts ??= []
target.forwardedPorts.push({
type: PortForwardType.Dynamic,
description: value,
host: bind.includes(':') ? bind.split(':')[0] : '127.0.0.1',
port: parseInt(at(bind.split(':'), -1)),
targetAddress: '',
targetPort: 22,
})
} else {
const mappedKey = {
hostname: 'host',
host: 'host',
port: 'port',
user: 'user',
forwardx11: 'x11',
serveraliveinterval: 'keepaliveInterval',
serveralivecountmax: 'keepaliveCountMax',
proxycommand: 'proxyCommand',
proxyjump: 'jumpHost',
}[key.toLowerCase()]
if (mappedKey) {
target[mappedKey] = value
}
}
}
}
if (currentProfile) {
results.push(currentProfile)
}
for (const p of results) {
if (p.options?.proxyCommand) {
p.options.proxyCommand = p.options.proxyCommand
.replace('%h', p.options.host ?? '')
.replace('%p', (p.options.port ?? 22).toString())
}
if (p.options?.jumpHost) {
p.options.jumpHost = deriveID(p.options.jumpHost)
}
}
return results
} catch (e) {
if (e.code === 'ENOENT') {
return []

View File

@@ -413,11 +413,6 @@ simple-swizzle@^0.2.2:
dependencies:
is-arrayish "^0.3.1"
ssh-config@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/ssh-config/-/ssh-config-5.0.1.tgz#44ee7db10d3340c79780afd142af05cf641408b9"
integrity sha512-Bh9CRGFq7pLpWFPmLOyirzYhbpme8FXZe3lZckWvmABdcIEiGB8tNbmEEZdppnr6EiQ0WcGTMoYDp8Tjomq9gw==
stack-trace@0.0.x:
version "0.0.10"
resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0"

View File

@@ -1,38 +1,27 @@
ul.nav-tabs(ngbNav, #nav='ngbNav')
li(ngbNavItem)
a(ngbNavLink, translate) General
ng-template(ngbNavContent)
command-line-editor([model]='profile.options')
command-line-editor([model]='profile.options')
.form-line(*ngIf='uac?.isAvailable')
.header
.title(translate) Run as administrator
toggle(
[(ngModel)]='profile.options.runAsAdministrator',
)
.form-line(*ngIf='uac?.isAvailable')
.header
.title(translate) Run as administrator
toggle(
[(ngModel)]='profile.options.runAsAdministrator',
)
.mb-3
label(translate) Working directory
.mb-3
label(translate) Working directory
.input-group
input.form-control(
type='text',
placeholder='Home directory',
[(ngModel)]='profile.options.cwd'
)
button.btn.btn-secondary((click)='pickWorkingDirectory()')
i.fas.fa-folder-open
.input-group
input.form-control(
type='text',
placeholder='Home directory',
[(ngModel)]='profile.options.cwd'
)
button.btn.btn-secondary((click)='pickWorkingDirectory()')
i.fas.fa-folder-open
.mb-3
label(translate) Environment
environment-editor(
type='text',
[(model)]='profile.options.env',
)
li(ngbNavItem)
a(ngbNavLink, translate) Colors
ng-template(ngbNavContent)
color-scheme-selector([(model)]='profile.terminalColorScheme')
div([ngbNavOutlet]='nav')
.mb-3
label(translate) Environment
environment-editor(
type='text',
[(model)]='profile.options.env',
)

View File

@@ -1,6 +1,6 @@
h3.mb-3(translate) Window
.form-line(*ngIf='themes.length > 1')
.form-line
.header
.title(translate) Theme
select.form-control(

View File

@@ -1,8 +1,7 @@
import * as fs from 'fs/promises'
import * as crypto from 'crypto'
// import * as fs from 'fs/promises'
import * as tmp from 'tmp-promise'
import { Injectable } from '@angular/core'
import { ConfigService, FileProvidersService, HostAppService, Platform, PlatformService } from 'tabby-core'
import { ConfigService, HostAppService, Platform, PlatformService } from 'tabby-core'
import { SSHSession } from '../session/ssh'
import { SSHProfile } from '../api'
import { PasswordStorageService } from './passwordStorage.service'
@@ -16,7 +15,6 @@ export class SSHService {
private config: ConfigService,
hostApp: HostAppService,
private platform: PlatformService,
private fileProviders: FileProvidersService,
) {
if (hostApp.platform === Platform.Windows) {
this.detectedWinSCPPath = platform.getWinSCPPath()
@@ -49,35 +47,14 @@ export class SSHService {
const args = [await this.getWinSCPURI(session.profile, undefined, session.authUsername ?? undefined)]
let tmpFile: tmp.FileResult|null = null
try {
if (session.activePrivateKey && session.profile.options.privateKeys && session.profile.options.privateKeys.length > 0) {
tmpFile = await tmp.file()
let passphrase: string|null = null
for (const pk of session.profile.options.privateKeys) {
let privateKeyContent: string|null = null
const buffer = await this.fileProviders.retrieveFile(pk)
privateKeyContent = buffer.toString()
await fs.writeFile(tmpFile.path, privateKeyContent)
const keyHash = crypto.createHash('sha512').update(privateKeyContent).digest('hex')
// need to pass an default passphrase, otherwise it might get stuck at the passphrase input
passphrase = await this.passwordStorage.loadPrivateKeyPassword(keyHash) ?? 'tabby'
const winSCPcom = path.slice(0, -3) + 'com'
try {
await this.platform.exec(winSCPcom, ['/keygen', tmpFile.path, '-o', tmpFile.path, '--old-passphrase', passphrase])
} catch (error) {
console.warn('Could not convert private key ', error)
continue
}
break
}
args.push(`/privatekey=${tmpFile.path}`)
if (passphrase != null) {
args.push(`/passphrase=${passphrase}`)
}
}
await this.platform.exec(path, args)
} finally {
tmpFile?.cleanup()
if (session.activePrivateKey) {
tmpFile = await tmp.file()
// await fs.writeFile(tmpFile.path, session.activePrivateKey)
const winSCPcom = path.slice(0, -3) + 'com'
await this.platform.exec(winSCPcom, ['/keygen', tmpFile.path, `/output=${tmpFile.path}`])
args.push(`/privatekey=${tmpFile.path}`)
}
await this.platform.exec(path, args)
tmpFile?.cleanup()
}
}

View File

@@ -50,18 +50,6 @@ type AuthMethod = {
kind: 'pageant',
}
function sshAuthTypeForMethod (m: AuthMethod): string {
switch (m.type) {
case 'none': return 'none'
case 'hostbased': return 'hostbased'
case 'prompt-password': return 'password'
case 'saved-password': return 'password'
case 'keyboard-interactive': return 'keyboard-interactive'
case 'publickey': return 'publickey'
case 'agent': return 'publickey'
}
}
export class KeyboardInteractivePrompt {
readonly responses: string[] = []
@@ -98,7 +86,7 @@ export class SSHSession {
ssh: russh.SSHClient|russh.AuthenticatedSSHClient
sftp?: russh.SFTP
forwardedPorts: ForwardedPort[] = []
jumpChannel: russh.NewChannel|null = null
jumpChannel: russh.Channel|null = null
savedPassword?: string
get serviceMessage$ (): Observable<string> { return this.serviceMessage }
get keyboardInteractivePrompt$ (): Observable<KeyboardInteractivePrompt> { return this.keyboardInteractivePrompt }
@@ -111,7 +99,7 @@ export class SSHSession {
private logger: Logger
private refCount = 0
private allAuthMethods: AuthMethod[] = []
private remainingAuthMethods: AuthMethod[] = []
private serviceMessage = new Subject<string>()
private keyboardInteractivePrompt = new Subject<KeyboardInteractivePrompt>()
private willDestroy = new Subject<void>()
@@ -125,7 +113,6 @@ export class SSHSession {
private translate: TranslateService
private knownHosts: SSHKnownHostsService
private privateKeyImporters: AutoPrivateKeyLocator[]
private previouslyDisconnected = false
constructor (
private injector: Injector,
@@ -150,34 +137,29 @@ export class SSHSession {
})
}
private addPublicKeyAuthMethod (name: string, contents: Buffer) {
this.allAuthMethods.push({
type: 'publickey',
name,
contents,
})
}
async init (): Promise<void> {
this.allAuthMethods = [{ type: 'none' }]
this.remainingAuthMethods = [{ type: 'none' }]
if (!this.profile.options.auth || this.profile.options.auth === 'publicKey') {
if (this.profile.options.privateKeys?.length) {
for (const pk of this.profile.options.privateKeys) {
// eslint-disable-next-line @typescript-eslint/init-declarations
let contents: Buffer
try {
contents = await this.fileProviders.retrieveFile(pk)
this.remainingAuthMethods.push({
type: 'publickey',
name: pk,
contents: await this.fileProviders.retrieveFile(pk),
})
} catch (error) {
this.emitServiceMessage(colors.bgYellow.yellow.black(' ! ') + ` Could not load private key ${pk}: ${error}`)
continue
}
this.addPublicKeyAuthMethod(pk, contents)
}
} else {
for (const importer of this.privateKeyImporters) {
for (const [name, contents] of await importer.getKeys()) {
this.addPublicKeyAuthMethod(name, contents)
this.remainingAuthMethods.push({
type: 'publickey',
name,
contents,
})
}
}
}
@@ -188,7 +170,7 @@ export class SSHSession {
if (!spec) {
this.emitServiceMessage(colors.bgYellow.yellow.black(' ! ') + ` Agent auth selected, but no running Agent process is found`)
} else {
this.allAuthMethods.push({
this.remainingAuthMethods.push({
type: 'agent',
...spec,
})
@@ -196,24 +178,22 @@ export class SSHSession {
}
if (!this.profile.options.auth || this.profile.options.auth === 'password') {
if (this.profile.options.password) {
this.allAuthMethods.push({ type: 'saved-password', password: this.profile.options.password })
this.remainingAuthMethods.push({ type: 'saved-password', password: this.profile.options.password })
}
const password = await this.passwordStorage.loadPassword(this.profile)
if (password) {
this.allAuthMethods.push({ type: 'saved-password', password })
this.remainingAuthMethods.push({ type: 'saved-password', password })
}
this.remainingAuthMethods.push({ type: 'prompt-password' })
}
if (!this.profile.options.auth || this.profile.options.auth === 'keyboardInteractive') {
const savedPassword = this.profile.options.password ?? await this.passwordStorage.loadPassword(this.profile)
if (savedPassword) {
this.allAuthMethods.push({ type: 'keyboard-interactive', savedPassword })
this.remainingAuthMethods.push({ type: 'keyboard-interactive', savedPassword })
}
this.allAuthMethods.push({ type: 'keyboard-interactive' })
this.remainingAuthMethods.push({ type: 'keyboard-interactive' })
}
if (!this.profile.options.auth || this.profile.options.auth === 'password') {
this.allAuthMethods.push({ type: 'prompt-password' })
}
this.allAuthMethods.push({ type: 'hostbased' })
this.remainingAuthMethods.push({ type: 'hostbased' })
}
private async getAgentConnectionSpec (): Promise<russh.AgentConnectionSpec|null> {
@@ -255,7 +235,7 @@ export class SSHSession {
throw new Error('Cannot open SFTP session before auth')
}
if (!this.sftp) {
this.sftp = await this.ssh.activateSFTP(await this.ssh.openSessionChannel())
this.sftp = await this.ssh.openSFTPChannel()
}
return new SFTPSession(this.sftp, this.injector)
}
@@ -276,7 +256,7 @@ export class SSHSession {
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(this.jumpChannel.take())
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}`)
@@ -326,14 +306,9 @@ export class SSHSession {
}
})
this.previouslyDisconnected = false
this.ssh.disconnect$.subscribe(() => {
if (!this.previouslyDisconnected) {
this.previouslyDisconnected = true
// Let service messages drain
setTimeout(() => {
this.destroy()
})
if (this.open) {
this.destroy()
}
})
@@ -384,31 +359,22 @@ export class SSHSession {
this.ssh.tcpChannelOpen$.subscribe(async event => {
this.logger.info(`Incoming forwarded connection: ${event.clientAddress}:${event.clientPort} -> ${event.targetAddress}:${event.targetPort}`)
if (!(this.ssh instanceof russh.AuthenticatedSSHClient)) {
throw new Error('Cannot open agent channel before auth')
}
const channel = await this.ssh.activateChannel(event.channel)
const forward = this.forwardedPorts.find(x => x.port === event.targetPort && x.host === event.targetAddress)
if (!forward) {
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Rejected incoming forwarded connection for unrecognized port ${event.targetAddress}:${event.targetPort}`)
channel.close()
return
}
const socket = new Socket()
socket.connect(forward.targetPort, forward.targetAddress)
socket.on('error', e => {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Could not forward the remote connection to ${forward.targetAddress}:${forward.targetPort}: ${e}`)
channel.close()
event.channel.close()
})
channel.data$.subscribe(data => socket.write(data))
socket.on('data', data => channel.write(Uint8Array.from(data)))
channel.closed$.subscribe(() => socket.destroy())
socket.on('close', () => channel.close())
event.channel.data$.subscribe(data => socket.write(data))
socket.on('data', data => event.channel.write(Uint8Array.from(data)))
event.channel.closed$.subscribe(() => socket.destroy())
socket.on('close', () => event.channel.close())
socket.on('connect', () => {
this.logger.info('Connection forwarded')
})
@@ -419,28 +385,22 @@ export class SSHSession {
const displaySpec = (this.config.store.ssh.x11Display || process.env.DISPLAY) ?? 'localhost:0'
this.logger.debug(`Trying display ${displaySpec}`)
if (!(this.ssh instanceof russh.AuthenticatedSSHClient)) {
throw new Error('Cannot open agent channel before auth')
}
const channel = await this.ssh.activateChannel(event.channel)
const socket = new X11Socket()
try {
const x11Stream = await socket.connect(displaySpec)
this.logger.info('Connection forwarded')
channel.data$.subscribe(data => {
event.channel.data$.subscribe(data => {
x11Stream.write(data)
})
x11Stream.on('data', data => {
channel.write(Uint8Array.from(data))
event.channel.write(Uint8Array.from(data))
})
channel.closed$.subscribe(() => {
event.channel.closed$.subscribe(() => {
socket.destroy()
})
x11Stream.on('close', () => {
channel.close()
event.channel.close()
})
} catch (e) {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
@@ -451,17 +411,11 @@ export class SSHSession {
this.emitServiceMessage(' * VcXsrv: https://sourceforge.net/projects/vcxsrv/')
this.emitServiceMessage(' * Xming: https://sourceforge.net/projects/xming/')
}
channel.close()
event.channel.close()
}
})
this.ssh.agentChannelOpen$.subscribe(async newChannel => {
if (!(this.ssh instanceof russh.AuthenticatedSSHClient)) {
throw new Error('Cannot open agent channel before auth')
}
const channel = await this.ssh.activateChannel(newChannel)
this.ssh.agentChannelOpen$.subscribe(async channel => {
const spec = await this.getAgentConnectionSpec()
if (!spec) {
await channel.close()
@@ -515,23 +469,7 @@ export class SSHSession {
this.keyboardInteractivePrompt.next(prompt)
}
async handleAuth (): Promise<russh.AuthenticatedSSHClient|null> {
const subscription = this.ssh.disconnect$.subscribe(() => {
// Auto auth and >=3 keys found
if (!this.profile.options.auth && this.allAuthMethods.filter(x => x.type === 'publickey').length >= 3) {
this.emitServiceMessage('The server has disconnected during authentication.')
this.emitServiceMessage('This may happen if too many private key authentication attemps are made.')
this.emitServiceMessage('You can set the specific private key for authentication in the profile settings.')
}
})
try {
return await this._handleAuth()
} finally {
subscription.unsubscribe()
}
}
private async _handleAuth (): Promise<russh.AuthenticatedSSHClient|null> {
async handleAuth (methodsLeft?: string[] | null): Promise<russh.AuthenticatedSSHClient|null> {
this.activePrivateKey = null
if (!(this.ssh instanceof russh.SSHClient)) {
@@ -542,37 +480,22 @@ export class SSHSession {
throw new Error('No username')
}
const noneResult = await this.ssh.authenticateNone(this.authUsername)
if (noneResult instanceof russh.AuthenticatedSSHClient) {
return noneResult
}
let remainingMethods = [...this.allAuthMethods]
let methodsLeft = noneResult.remainingMethods
function maybeSetRemainingMethods (r: russh.AuthFailure) {
if (r.remainingMethods.length) {
methodsLeft = r.remainingMethods
}
}
while (true) {
const m = methodsLeft
const method = remainingMethods.find(x => m.length === 0 || m.includes(sshAuthTypeForMethod(x)))
if (this.previouslyDisconnected || !method) {
const method = this.remainingAuthMethods.shift()
if (!method) {
return null
}
remainingMethods = remainingMethods.filter(x => x !== method)
if (methodsLeft && !methodsLeft.includes(method.type) && method.type !== 'agent') {
// Agent can still be used even if not in methodsLeft
this.logger.info('Server does not support auth method', method.type)
continue
}
if (method.type === 'saved-password') {
this.emitServiceMessage(this.translate.instant('Using saved password'))
const result = await this.ssh.authenticateWithPassword(this.authUsername, method.password)
if (result instanceof russh.AuthenticatedSSHClient) {
if (result) {
return result
}
maybeSetRemainingMethods(result)
}
if (method.type === 'prompt-password') {
const modal = this.ngbModal.open(PromptModalComponent)
@@ -587,10 +510,9 @@ export class SSHSession {
this.savedPassword = promptResult.value
}
const result = await this.ssh.authenticateWithPassword(this.authUsername, promptResult.value)
if (result instanceof russh.AuthenticatedSSHClient) {
if (result) {
return result
}
maybeSetRemainingMethods(result)
} else {
continue
}
@@ -601,12 +523,10 @@ export class SSHSession {
if (method.type === 'publickey') {
try {
const key = await this.loadPrivateKey(method.name, method.contents)
this.emitServiceMessage(`Trying private key: ${method.name}`)
const result = await this.ssh.authenticateWithKeyPair(this.authUsername, key, null)
if (result instanceof russh.AuthenticatedSSHClient) {
const result = await this.ssh.authenticateWithKeyPair(this.authUsername, key)
if (result) {
return result
}
maybeSetRemainingMethods(result)
} catch (e) {
this.emitServiceMessage(colors.bgYellow.yellow.black(' ! ') + ` Failed to load private key ${method.name}: ${e}`)
continue
@@ -617,7 +537,6 @@ export class SSHSession {
while (true) {
if (state.state === 'failure') {
maybeSetRemainingMethods(state)
break
}
@@ -653,7 +572,7 @@ export class SSHSession {
}
}
state = await this.ssh.continueKeyboardInteractiveAuthentication(responses)
state = await this.ssh .continueKeyboardInteractiveAuthentication(responses)
if (state instanceof russh.AuthenticatedSSHClient) {
return state
@@ -663,10 +582,9 @@ export class SSHSession {
if (method.type === 'agent') {
try {
const result = await this.ssh.authenticateWithAgent(this.authUsername, method)
if (result instanceof russh.AuthenticatedSSHClient) {
if (result) {
return result
}
maybeSetRemainingMethods(result)
} catch (e) {
this.emitServiceMessage(colors.bgYellow.yellow.black(' ! ') + ` Failed to authenticate using agent: ${e}`)
continue
@@ -685,7 +603,7 @@ export class SSHSession {
reject()
return
}
const channel = await this.ssh.activateChannel(await this.ssh.openTCPForwardChannel({
const channel = await this.ssh.openTCPForwardChannel({
addressToConnectTo: targetAddress,
portToConnectTo: targetPort,
originatorAddress: sourceAddress ?? '127.0.0.1',
@@ -694,7 +612,7 @@ export class SSHSession {
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Remote has rejected the forwarded connection to ${targetAddress}:${targetPort} via ${fw}: ${err}`)
reject()
throw err
}))
})
const socket = accept()
channel.data$.subscribe(data => socket.write(data))
socket.on('data', data => channel.write(Uint8Array.from(data)))
@@ -751,7 +669,7 @@ export class SSHSession {
if (!(this.ssh instanceof russh.AuthenticatedSSHClient)) {
throw new Error('Cannot open shell channel before auth')
}
const ch = await this.ssh.activateChannel(await this.ssh.openSessionChannel())
const ch = await this.ssh.openSessionChannel()
await ch.requestPTY('xterm-256color', {
columns: 80,
rows: 24,
@@ -774,15 +692,13 @@ export class SSHSession {
}
async loadPrivateKey (name: string, privateKeyContents: Buffer): Promise<russh.KeyPair> {
this.emitServiceMessage(`Loading private key: ${name}`)
this.activePrivateKey = await this.loadPrivateKeyWithPassphraseMaybe(privateKeyContents.toString())
return this.activePrivateKey
}
async loadPrivateKeyWithPassphraseMaybe (privateKey: string): Promise<russh.KeyPair> {
const keyHash = crypto.createHash('sha512').update(privateKey).digest('hex')
privateKey = privateKey.replaceAll('EC PRIVATE KEY', 'PRIVATE KEY')
let triedSavedPassphrase = false
let passphrase: string|null = null
while (true) {
@@ -794,12 +710,7 @@ export class SSHSession {
triedSavedPassphrase = true
continue
}
if ([
'Error: Keys(KeyIsEncrypted)',
'Error: Keys(SshKey(Ppk(Encrypted)))',
'Error: Keys(SshKey(Ppk(IncorrectMac)))',
'Error: Keys(SshKey(Crypto))',
].includes(e.toString())) {
if (e.toString() === 'Error: Keys(KeyIsEncrypted)' || e.toString() === 'Error: Keys(SshKey(Crypto))') {
await this.passwordStorage.deletePrivateKeyPassword(keyHash)
const modal = this.ngbModal.open(PromptModalComponent)

View File

@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core'
import { TerminalDecorator, BaseTerminalTabComponent } from 'tabby-terminal'
import { webUtils } from 'electron'
import { TerminalDecorator } from '../api/decorator'
import { BaseTerminalTabComponent } from '../api/baseTerminalTab.component'
/** @hidden */
@Injectable()
@@ -11,8 +11,8 @@ export class PathDropDecorator extends TerminalDecorator {
event.preventDefault()
}))
this.subscribeUntilDetached(terminal, terminal.frontend?.drop$.subscribe((event: DragEvent) => {
for (const file of event.dataTransfer!.files as unknown as Iterable<File>) {
this.injectPath(terminal, webUtils.getPathForFile(file))
for (const file of event.dataTransfer!.files as any) {
this.injectPath(terminal, file.path)
}
event.preventDefault()
}))

View File

@@ -26,6 +26,7 @@ import { TerminalContextMenuItemProvider } from './api/contextMenuProvider'
import { TerminalColorSchemeProvider } from './api/colorSchemeProvider'
import { TerminalSettingsTabProvider, AppearanceSettingsTabProvider, ColorSchemeSettingsTabProvider } from './settings'
import { DebugDecorator } from './features/debug'
import { PathDropDecorator } from './features/pathDrop'
import { ZModemDecorator } from './features/zmodem'
import { TerminalConfigProvider } from './config'
import { TerminalHotkeyProvider } from './hotkeys'
@@ -53,6 +54,7 @@ import { DefaultColorSchemes } from './colorSchemes'
{ provide: ConfigProvider, useClass: TerminalConfigProvider, multi: true },
{ provide: HotkeyProvider, useClass: TerminalHotkeyProvider, multi: true },
{ provide: TerminalDecorator, useClass: PathDropDecorator, multi: true },
{ provide: TerminalDecorator, useClass: ZModemDecorator, multi: true },
{ provide: TerminalDecorator, useClass: DebugDecorator, multi: true },

View File

@@ -180,7 +180,9 @@ export class SaveAsProfileContextMenu extends TabContextMenuItemProvider {
return
}
const options = JSON.parse(JSON.stringify(tab.profile.options))
const options = {
...tab.profile.options,
}
const cwd = await tab.session?.getWorkingDirectory() ?? tab.profile.options.cwd
if (cwd) {

View File

@@ -11,4 +11,5 @@ const paths = [
paths.forEach(x => log.info(`Using config: ${x}`))
export default () => Promise.all(paths.map(x => import(x).then(x => x.default())))
const config = await Promise.all(paths.map(x => import(x).then(x => x.default())))
export default () => config

View File

@@ -161,8 +161,8 @@ export default options => {
'@luminati-io/socksv5',
'stream',
'windows-native-registry',
'@tabby-gang/windows-process-tree',
'@tabby-gang/windows-process-tree/build/Release/windows_process_tree.node',
'windows-process-tree',
'windows-process-tree/build/Release/windows_process_tree.node',
/^@angular(?!\/common\/locales)/,
/^@ng-bootstrap/,
/^rxjs/,

994
yarn.lock

File diff suppressed because it is too large Load Diff