diff --git a/.github/workflows/winget.yml b/.github/workflows/winget.yml
new file mode 100644
index 000000000..82243264c
--- /dev/null
+++ b/.github/workflows/winget.yml
@@ -0,0 +1,12 @@
+name: Publish to WinGet
+on:
+  release:
+    types: [released]
+jobs:
+  publish:
+    runs-on: windows-latest # action can only be run on windows
+    steps:
+      - uses: vedantmgoyal2009/winget-releaser@latest
+        with:
+          identifier: RustDesk.RustDesk
+          token: ${{ secrets.WINGET_TOKEN }}
diff --git a/README-AR.md b/README-AR.md
new file mode 100644
index 000000000..055a654d2
--- /dev/null
+++ b/README-AR.md
@@ -0,0 +1,190 @@
+<p align="center">
+  <img src="logo-header.svg" alt="RustDesk - Your remote desktop"><br>
+  <a href="#free-public-servers">Servers</a> •
+  <a href="#raw-steps-to-build">Build</a> •
+  <a href="#how-to-build-with-docker">Docker</a> •
+  <a href="#file-structure">Structure</a> •
+  <a href="#snapshot">Snapshot</a><br>
+  [<a href="README-CS.md">česky</a>] | [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
+  <b>  لغتك الأم,  <a href="https://github.com/rustdesk/doc.rustdesk.com">Doc</a> و <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">RustDesk UI</a>, README نحن بحاجة إلى مساعدتك لترجمة هذا </b>
+</p>
+
+[Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) :تواصل معنا عبر
+
+[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09)
+
+.Rustبرنامج آخر لسطح المكتب عن بعد، مكتوب بـ
+يعمل خارج الصندوق، لا حاجة إلى إعدادات. لديك سيطرة كاملة على بياناتك، دون مخاوف بشأن الأمن. يمكنك استخدام خادم
+  الخاص بنا rendezvous/relay
+[جهز لنفسك واحدا](https://rustdesk.com/server), أو
+[خاص بك rendezvous/relay أكتب خادم](https://github.com/rustdesk/rustdesk-server-demo).
+
+![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png)
+
+لمساعدتك على ذلك [`CONTRIBUTING.md`](CONTRIBUTING.md) يرحب بمساهمة الجميع. اطلع على  RustDesk.
+
+[**؟ RustDesk كيفية يعمل**](https://github.com/rustdesk/rustdesk/wiki/How-does-RustDesk-work%3F)
+
+[**BINARY تنزيل**](https://github.com/rustdesk/rustdesk/releases)
+
+## خوادم مفتوحة ومجانية
+
+فيما يلي الخوادم التي تستخدمها مجانًا، وقد تتغير طوال الوقت. إذا لم تكن قريبًا من أحد هؤلاء، فقد تكون شبكتك بطيئة.
+| الموقع | المورد | المواصفات |
+| --------- | ------------- | ------------------ |
+| Seoul | AWS lightsail | 1 VCPU / 0.5GB RAM |
+| Singapore | Vultr | 1 VCPU / 1GB RAM |
+| Dallas | Vultr | 1 VCPU / 1GB RAM | |
+
+## التبعيات
+
+ لواجهة المستخدم الرسومية [sciter](https://sciter.com/) نسخة سطح المكتب تستخدم
+ بنفسك sciter dynamic library عليك تحميل
+
+[Windows](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.win/x64/sciter.dll) |
+[Linux](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so) |
+[MacOS](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.osx/libsciter.dylib)
+
+ Sciter إلى Flutter سنقوم بترحيل نسخة سطح المكتب من .Flutter تستخدم إصدارات الهاتف المحمول.
+
+## خطوات البناء
+
+- C++ build env و Rust development env قم بإعداد
+
+- بطريقة صحيحة `VCPKG_ROOT` env variable وأعد [vcpkg](https://github.com/microsoft/vcpkg) ثبت
+
+  - Windows: `vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static`
+  - Linux/MacOS: `vcpkg install libvpx libyuv opus`
+
+- run `cargo run`
+
+## [البناء](https://rustdesk.com/docs/en/dev/build/)
+
+## Linux
+
+### Ubuntu 18 (Debian 10)
+
+```sh
+sudo apt install -y g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake
+```
+
+### Fedora 28 (CentOS 8)
+
+```sh
+sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel
+```
+
+### Arch (Manjaro)
+
+```sh
+sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pulseaudio
+```
+
+### pynput package تثبيت
+
+```sh
+pip3 install pynput
+```
+
+### vcpkg تثبيت
+
+```sh
+git clone https://github.com/microsoft/vcpkg
+cd vcpkg
+git checkout 2021.12.01
+cd ..
+vcpkg/bootstrap-vcpkg.sh
+export VCPKG_ROOT=$HOME/vcpkg
+vcpkg/vcpkg install libvpx libyuv opus
+```
+
+### Fix libvpx (For Fedora)
+
+```sh
+cd vcpkg/buildtrees/libvpx/src
+cd *
+./configure
+sed -i 's/CFLAGS+=-I/CFLAGS+=-fPIC -I/g' Makefile
+sed -i 's/CXXFLAGS+=-I/CXXFLAGS+=-fPIC -I/g' Makefile
+make
+cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/
+cd
+```
+
+### البناء
+
+```sh
+curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
+source $HOME/.cargo/env
+git clone https://github.com/rustdesk/rustdesk
+cd rustdesk
+mkdir -p target/debug
+wget https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so
+mv libsciter-gtk.so target/debug
+VCPKG_ROOT=$HOME/vcpkg cargo run
+```
+
+###  X11 (Xorg) إلى Wayland تغيير
+
+افتراضية GNOME session  ك Xorg إتبع [هذه](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) الخطوات لإعداد Wayland لا تدعم RustDesk
+
+## Docker طريقة البناء باستخدام
+
+ابدأ باستنساخ المستودع وبناء الكونتاينر:
+
+```sh
+git clone https://github.com/rustdesk/rustdesk
+cd rustdesk
+docker build -t "rustdesk-builder" .
+```
+
+ثم، في كل مرة تحتاج إلى بناء التطبيق، قم بتشغيل الأمر التالي:
+
+```sh
+docker run --rm -it -v $PWD:/home/user/rustdesk -v rustdesk-git-cache:/home/user/.cargo/git -v rustdesk-registry-cache:/home/user/.cargo/registry -e PUID="$(id -u)" -e PGID="$(id -g)" rustdesk-builder
+```
+
+لاحظ أن البناء الأول قد يستغرق وقتًا أطول قبل تخزين التبعيات، وسيكون البناء اللاحق أسرع. بالإضافة إلى ذلك، إذا كنت بحاجة إلى تحديد وسائط مختلفة لأمر البناء، فيمكنك القيام بذلك في نهاية الأمر بوضع
+`<OPTIONAL-ARGS>`
+على سبيل المثال، إذا كنت ترغب في بناء إصدار محسن، فستقوم بتشغيل الأمر أعلاه متبوعًا بـ
+`--release`
+:سيكون الملف القابل للتنفيذ الناتج متاحًا في مجلد تارغت، ويمكن تشغيله باستخدام
+
+```sh
+target/debug/rustdesk
+```
+
+:أو في حال قمت ببناء إصدار محسن
+
+```sh
+target/release/rustdesk
+```
+
+RustDesk يرجى التأكد من أنك تنفذ هذه الأوامر من جذر مستودع
+وإلا فقد لا يتمكن التطبيق من العثور على الموارد المطلوبة. لاحظ أيضًا أن الأوامر الفرعية الأخرى مثل
+`install` أو `run`
+لا يتم دعمها حاليًا عبر هذه الطريقة لأنها ستقوم بتثبيت أو تشغيل البرنامج داخل الكونتاينر بدلاً من الهوست.
+
+## هيكل الملف
+
+- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: وظائف  لنقل الملفات، وبعض وظائف المرافق الأخرى tcp/udp، protobuf ترميز الفيديو، إعدادات
+
+- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: التقاط الشاشة
+- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: التحكم في لوحة المفاتيح/الماوس الخاصة بكل منصة
+- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: واجهة المستخدم الرسومية
+- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: خدمات الصوت/الحافظة/المدخلات/الفيديو، ووصلات الشبكة
+- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: بدء اتصال متقارن
+- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: أو المنقول عن بُعد (TCP hole punching) انتظر الاتصال المباشر [rustdesk-server](https://github.com/rustdesk/rustdesk-server) الإتصال ب
+- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: رمز خاص بكل منصة
+- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: رمز الهاتف المحمول
+- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**:Flutter  لعميل الويب الخاص ب Javascript
+
+## لقطات
+
+![image](https://user-images.githubusercontent.com/71636191/113112362-ae4deb80-923b-11eb-957d-ff88daad4f06.png)
+
+![image](https://user-images.githubusercontent.com/71636191/113112619-f705a480-923b-11eb-911d-97e984ef52b6.png)
+
+![image](https://user-images.githubusercontent.com/71636191/113112857-3fbd5d80-923c-11eb-9836-768325faf906.png)
+
+![image](https://user-images.githubusercontent.com/71636191/135385039-38fdbd72-379a-422d-b97f-33df71fb1cec.png)
diff --git a/README-CS.md b/README-CS.md
index 2606e2def..1dd5463a1 100644
--- a/README-CS.md
+++ b/README-CS.md
@@ -5,7 +5,7 @@
   <a href="#how-to-build-with-docker">Docker</a> •
   <a href="#file-structure">Struktura</a> •
   <a href="#snapshot">Ukázky</a><br>
-  [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>]<br>
+  [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
   <b>Potřebujeme Vaši pomoc s překláním textů tohoto ČTIMNE, <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">uživatelského rozhraní aplikace RustDesk</a> a <a href="https://github.com/rustdesk/doc.rustdesk.com">dokumentace k ní</a> do vašeho jazyka</b>
 </p>
 
diff --git a/README-DE.md b/README-DE.md
index 217fbc76a..3f770d226 100644
--- a/README-DE.md
+++ b/README-DE.md
@@ -5,7 +5,7 @@
   <a href="#auf-docker-kompilieren">Docker</a> •
   <a href="#dateistruktur">Dateistruktur</a> •
   <a href="#screenshots">Screenshots</a><br>
-  [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>]<br>
+  [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
   <b>Wir brauchen deine Hilfe um diese README Datei zu verbessern und aktualisieren</b>
 </p>
 
diff --git a/README-EO.md b/README-EO.md
index 224caa064..21a4f9521 100644
--- a/README-EO.md
+++ b/README-EO.md
@@ -5,7 +5,7 @@
   <a href="#kiel-kompili-kun-docker">Docker</a> •
   <a href="#dosierstrukturo">Strukturo</a> •
   <a href="#ekrankopio">Ekrankopio</a><br>
-  [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>]<br>
+  [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
   <b>Ni bezonas helpon traduki tiun README kaj <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">la interfacon</a> al via denaska lingvo</b>
 </p>
 
diff --git a/README-ES.md b/README-ES.md
index 19a0484ef..1c0b5dd7b 100644
--- a/README-ES.md
+++ b/README-ES.md
@@ -5,7 +5,7 @@
   <a href="#como-compilar-con-docker">Docker</a> •
   <a href="#estructura-de-archivos">Estructura</a> •
   <a href="#captura-de-pantalla">Captura de pantalla</a><br>
-  [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>]<br>
+  [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
   <b>Necesitamos tu ayuda para traducir este README a tu idioma</b>
 </p>
 
diff --git a/README-FA.md b/README-FA.md
index 33b9f29bd..0f7ca1a95 100644
--- a/README-FA.md
+++ b/README-FA.md
@@ -5,7 +5,7 @@
   <a dir="rtl" href="#نحوه-ساخت-با-داکر">داکر</a> •
   <a dir="rtl" href="#ساخت">ساخت</a> •
   <a dir="rtl" href="#سرورهای-عمومی-رایگان">سرور</a><br>
-  [<a href="README-CS.md">česky</a>] | [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>]<br>
+  [<a href="README-CS.md">česky</a>] | [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
   &#x202b;<b>برای ترجمه این  <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang"> RustDesk UI</a>  ،README  و <a href="https://github.com/rustdesk/doc.rustdesk.com">Doc</a> به زبان مادری شما به کمکتون نیاز داریم
 </p>
 
diff --git a/README-FI.md b/README-FI.md
index ea923170e..a2d7534e0 100644
--- a/README-FI.md
+++ b/README-FI.md
@@ -5,7 +5,7 @@
   <a href="#how-to-build-with-docker">Docker</a> •
   <a href="#file-structure">Rakenne</a> •
   <a href="#snapshot">Tilannevedos</a><br>
-  [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>]<br>
+  [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
   <b>Tarvitsemme apua tämän README-tiedoston kääntämiseksi äidinkielellesi</b>
 </p>
 
diff --git a/README-FR.md b/README-FR.md
index 9dbeaecf2..b1f8e3670 100644
--- a/README-FR.md
+++ b/README-FR.md
@@ -5,7 +5,7 @@
   <a href="#comment-construire-avec-docker">Docker</a> -
   <a href="#structure-du-projet">Structure</a> -
   <a href="#images">Images</a><br>
-  [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>]<br>
+  [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
   <b>Nous avons besoin de votre aide pour traduire ce README dans votre langue maternelle</b>.
 </p>
 
diff --git a/README-ID.md b/README-ID.md
index 31400a945..624336f45 100644
--- a/README-ID.md
+++ b/README-ID.md
@@ -5,7 +5,7 @@
   <a href="#how-to-build-with-docker">Docker</a> •
   <a href="#file-structure">Structure</a> •
   <a href="#snapshot">Snapshot</a><br>
-  [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>]<br>
+  [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
   <b>Kami membutuhkan bantuan Anda untuk menerjemahkan README ini dan <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">RustDesk UI</a> ke bahasa asli anda</b>
 </p>
 
diff --git a/README-IT.md b/README-IT.md
index 6a0df6ac6..7eba7860a 100644
--- a/README-IT.md
+++ b/README-IT.md
@@ -5,7 +5,7 @@
   <a href="#come-compilare-con-docker">Docker</a> •
   <a href="#struttura-dei-file">Struttura</a> •
   <a href="#screenshots">Screenshots</a><br>
-  [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>]<br>
+  [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
   <b>Abbiamo bisogno del tuo aiuto per tradurre questo README e la <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">RustDesk UI</a> nella tua lingua nativa</b>
 </p>
 
diff --git a/README-JP.md b/README-JP.md
index a7858b332..60816a5d5 100644
--- a/README-JP.md
+++ b/README-JP.md
@@ -5,7 +5,7 @@
   <a href="#how-to-build-with-docker">Docker</a> •
   <a href="#file-structure">Structure</a> •
   <a href="#snapshot">Snapshot</a><br>
-  [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>]<br>
+  [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
   <b>このREADMEをあなたの母国語に翻訳するために、あなたの助けが必要です。</b>
 </p>
 
diff --git a/README-KR.md b/README-KR.md
index 9652e978a..750cf91bd 100644
--- a/README-KR.md
+++ b/README-KR.md
@@ -5,7 +5,7 @@
   <a href="#how-to-build-with-docker">Docker</a> •
   <a href="#file-structure">Structure</a> •
   <a href="#snapshot">Snapshot</a><br>
-  [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>]<br>
+  [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
   <b>README를 모국어로 번역하기 위한 당신의 도움의 필요합니다.</b>
 </p>
 
diff --git a/README-ML.md b/README-ML.md
index 69e3269cd..c479d0496 100644
--- a/README-ML.md
+++ b/README-ML.md
@@ -5,7 +5,7 @@
   <a href="#how-to-build-with-docker">Docker</a> •
   <a href="#file-structure">Structure</a> •
   <a href="#snapshot">Snapshot</a><br>
-  [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>]<br>
+  [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
   <b>ഈ README നിങ്ങളുടെ മാതൃഭാഷയിലേക്ക് വിവർത്തനം ചെയ്യാൻ ഞങ്ങൾക്ക് നിങ്ങളുടെ സഹായം ആവശ്യമാണ്</b>
 </p>
 
diff --git a/README-NL.md b/README-NL.md
index 0b355a273..2d87504db 100644
--- a/README-NL.md
+++ b/README-NL.md
@@ -5,7 +5,7 @@
   <a href="#how-to-build-with-docker">Docker</a> •
   <a href="#file-structure">Structuur</a> •
   <a href="#snapshot">Snapshot</a><br>
-  [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>]<br>
+  [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
   <b>We hebben je hulp nodig om deze README te vertalen naar jouw moedertaal</b>
 </p>
 
diff --git a/README-PL.md b/README-PL.md
index 2922eae0d..162ca7648 100644
--- a/README-PL.md
+++ b/README-PL.md
@@ -5,7 +5,7 @@
   <a href="#jak-kompilować-za-pomocą-dockera">Docker</a> •
   <a href="#struktura-plików">Struktura</a> •
   <a href="#migawkisnapshoty">Snapshot</a><br>
-  [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>]<br>
+  [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
   <b>Potrzebujemy twojej pomocy w tłumaczeniu README na twój ojczysty język</b>
 </p>
 
diff --git a/README-PTBR.md b/README-PTBR.md
index 7eff79566..76b360283 100644
--- a/README-PTBR.md
+++ b/README-PTBR.md
@@ -5,7 +5,7 @@
   <a href="#como-compilar-com-docker">Docker</a> •
   <a href="#estrutura-de-arquivos">Estrutura</a> •
   <a href="#screenshots">Screenshots</a><br>
-  [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>]<br>
+  [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
   <b>Precisamos de sua ajuda para traduzir este README e a <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">UI do RustDesk</a> para sua língua nativa</b>
 </p>
 
diff --git a/README-RU.md b/README-RU.md
index ffc1256d4..54c161cf0 100644
--- a/README-RU.md
+++ b/README-RU.md
@@ -5,7 +5,7 @@
   <a href="#how-to-build-with-docker">Docker</a> •
   <a href="#file-structure">Structure</a> •
   <a href="#snapshot">Snapshot</a><br>
-  [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>]<br>
+  [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
   <b>Нам нужна ваша помощь для перевода этого README и <a href="https://github.com/rustdesk/rustdesk/tree/master/src/rustdesk/tree/master/src/lang">RustDesk UI</a> на ваш родной язык</B>
 </p>
 
diff --git a/README-ZH.md b/README-ZH.md
index 00176a1c4..8d4203b16 100644
--- a/README-ZH.md
+++ b/README-ZH.md
@@ -5,7 +5,7 @@
   <a href="#使用Docker编译">Docker</a> •
   <a href="#文件结构">结构</a> •
   <a href="#截图">截图</a><br>
-  [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>]<br>
+  [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
 </p>
 
 Chat with us: [知乎](https://www.zhihu.com/people/rustdesk) | [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk)
diff --git a/README.md b/README.md
index a9ddc6071..613e425e2 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
   <a href="#how-to-build-with-docker">Docker</a> •
   <a href="#file-structure">Structure</a> •
   <a href="#snapshot">Snapshot</a><br>
-  [<a href="README-CS.md">česky</a>] | [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>]<br>
+  [<a href="README-CS.md">česky</a>] | [<a href="README-ZH.md">中文</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>]<br>
   <b>We need your help to translate this README, <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">RustDesk UI</a> and <a href="https://github.com/rustdesk/doc.rustdesk.com">Doc</a> to your native language</b>
 </p>
 
diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt
index e32132ee7..966ad3df8 100644
--- a/fastlane/metadata/android/en-US/full_description.txt
+++ b/fastlane/metadata/android/en-US/full_description.txt
@@ -1 +1,11 @@
-Yet another remote desktop software, written in Rust. Works out of the box, no configuration required. You have full control of your data, with no concerns about security. You can use our rendezvous/relay server, <a href="https://rustdesk.com/server">set up your own</a>, or <a href="https://github.com/rustdesk/rustdesk-server-demo">write your own rendezvous/relay server</a>.
\ No newline at end of file
+An open-source remote desktop application, the open source TeamViewer alternative.
+Source code: https://github.com/rustdesk/rustdesk
+Doc: https://rustdesk.com/docs/en/manual/mobile/
+
+In order for a remote device to control your Android device via mouse or touch, you need to allow RustDesk to use the "Accessibility" service, RustDesk uses AccessibilityService API to implement Addroid remote control.
+
+In addtion to remote control, you can also transfer files between Android devices and PCs easily with RustDesk.
+
+You have full control of your data, with no concerns about security. You can use our rendezvous/relay server, or self-hosting, or write your own rendezvous/relay server. Self-hosting server is free and open source: https://github.com/rustdesk/rustdesk-server
+
+Please download and install desktop version from: https://rustdesk.com, then you can access and control your desktop from your mobile, or control your mobile from desktop.
diff --git a/fastlane/metadata/android/en-US/short_description.txt b/fastlane/metadata/android/en-US/short_description.txt
index b88c8e1d7..357fb37ab 100644
--- a/fastlane/metadata/android/en-US/short_description.txt
+++ b/fastlane/metadata/android/en-US/short_description.txt
@@ -1 +1 @@
-Yet another remote desktop software
\ No newline at end of file
+An open-source remote desktop application, the open source TeamViewer alternative.
diff --git a/fastlane/metadata/android/zh-CN/full_description.txt b/fastlane/metadata/android/zh-CN/full_description.txt
index 51fad180b..f1f44057e 100644
--- a/fastlane/metadata/android/zh-CN/full_description.txt
+++ b/fastlane/metadata/android/zh-CN/full_description.txt
@@ -1,3 +1,12 @@
-远程桌面软件,开箱即用,无需任何配置。您完全掌控数据,不用担心安全问题。您可以使用我们的注册/中继服务器,
-或者<a href="https://rustdesk.com/server">自己设置</a>,
-亦或者<a href="https://github.com/rustdesk/rustdesk-server-demo">开发您的版本</a>。
\ No newline at end of file
+开源远程桌面应用,开源 TeamViewer 替代方案。
+源代码:https://github.com/rustdesk/rustdesk
+文档:https://rustdesk.com/docs/en/manual/mobile/
+
+为了让远程设备通过鼠标或触摸控制您的 Android 设备,您需要允许 RustDesk 使用“Accessibility”服务,RustDesk 使用 AccessibilityService API 来实现 Addroid 远程控制。
+
+除了远程控制,您还可以使用 RustDesk 在 Android 设备和 PC 之间轻松传输文件。
+
+您完全掌控数据,不用担心安全问题。您可以使用我们的注册/中继服务器,或者自建,亦或者开发您的版本。
+自托管服务器是免费和开源的:https://github.com/rustdesk/rustdesk-server
+
+请从:https://rustdesk.com 下载并安装桌面版,然后您可以通过手机访问和控制您的桌面,或从桌面控制您的手机。
diff --git a/fastlane/metadata/android/zh-CN/short_description.txt b/fastlane/metadata/android/zh-CN/short_description.txt
index ac41efbee..69a4a5b52 100644
--- a/fastlane/metadata/android/zh-CN/short_description.txt
+++ b/fastlane/metadata/android/zh-CN/short_description.txt
@@ -1 +1 @@
-远程桌面软件
\ No newline at end of file
+开源远程桌面应用,开源 TeamViewer 替代方案
diff --git a/flutter/build_android_deps.sh b/flutter/build_android_deps.sh
index 566196d52..f120346cf 100755
--- a/flutter/build_android_deps.sh
+++ b/flutter/build_android_deps.sh
@@ -4,7 +4,7 @@
 # Required: 
 #   1. set VCPKG_ROOT / ANDROID_NDK path environment variables
 #   2. vcpkg initialized
-#   3. ndk >= 22 (if ndk< 22 you need to change LD as `export LD=$TOOLCHAIN/bin/aarch64-linux-android-ld`)
+#   3. ndk, version: 22 (if ndk < 22 you need to change LD as `export LD=$TOOLCHAIN/bin/$NDK_LLVM_TARGET-ld`)
 
 if [ -z "$ANDROID_NDK" ]; then
   echo "Failed! Please set ANDROID_NDK"
@@ -16,66 +16,110 @@ if [ -z "$VCPKG_ROOT" ]; then
   exit 1
 fi
 
-PREFIX=$VCPKG_ROOT/installed/arm64-android/
+API_LEVEL="21"
 
-echo "*** [Start] Build opus / libyuv from vcpkg"
-export ANDROID_NDK_HOME=$ANDROID_NDK
-pushd $VCPKG_ROOT
-$VCPKG_ROOT/vcpkg install opus --triplet arm64-android
-$VCPKG_ROOT/vcpkg install libyuv --triplet arm64-android
-popd
-echo "*** [Finished] Build opus / libyuv from vcpkg"
+# NDK llvm toolchain
+HOST_TAG="linux-x86_64" # current platform, set as `ls $ANDROID_NDK/toolchains/llvm/prebuilt/`
+TOOLCHAIN=$ANDROID_NDK/toolchains/llvm/prebuilt/$HOST_TAG
+
+function build {
+  ANDROID_ABI=$1 
+  VCPKG_TARGET=$2
+  NDK_LLVM_TARGET=$3
+  LIBVPX_TARGET=$4
+
+  PREFIX=$VCPKG_ROOT/installed/$VCPKG_TARGET/
+
+  # 1
+  echo "*** [$ANDROID_ABI][Start] Build opus / libyuv from vcpkg"
+  export ANDROID_NDK_HOME=$ANDROID_NDK
+  pushd $VCPKG_ROOT
+  $VCPKG_ROOT/vcpkg install opus --triplet $VCPKG_TARGET
+  $VCPKG_ROOT/vcpkg install libyuv --triplet $VCPKG_TARGET
+  popd
+  echo "*** [$ANDROID_ABI][Finished] Build opus / libyuv from vcpkg"
+
+  # 2
+  echo "*** [$ANDROID_ABI][Start] Build libvpx"
+  pushd build/libvpx
+  export AR=$TOOLCHAIN/bin/${NDK_LLVM_TARGET}-ar
+  export AS=$TOOLCHAIN/bin/${NDK_LLVM_TARGET}-as
+  export LD=$TOOLCHAIN/bin/${NDK_LLVM_TARGET}-ld.gold  # if ndk < 22, use aarch64-linux-android-ld
+  export RANLIB=$TOOLCHAIN/bin/${NDK_LLVM_TARGET}-ranlib
+  export STRIP=$TOOLCHAIN/bin/${NDK_LLVM_TARGET}-strip
+
+  if [ $NDK_LLVM_TARGET == "arm-linux-androideabi" ]
+  then
+    export CC=$TOOLCHAIN/bin/armv7a-linux-androideabi${API_LEVEL}-clang
+    export CXX=$TOOLCHAIN/bin/armv7a-linux-androideabi${API_LEVEL}-clang++
+  else
+    export CC=$TOOLCHAIN/bin/${NDK_LLVM_TARGET}${API_LEVEL}-clang
+    export CXX=$TOOLCHAIN/bin/${NDK_LLVM_TARGET}${API_LEVEL}-clang++
+  fi
+  make clean
+  ./configure --target=$LIBVPX_TARGET \
+              --enable-pic --disable-vp8 \
+              --disable-webm-io \
+              --disable-unit-tests \
+              --disable-examples \
+              --disable-libyuv \
+              --disable-postproc \
+              --disable-vp8 \
+              --disable-tools \
+              --disable-docs \
+              --prefix=$PREFIX
+  make -j5
+  make install
+
+  popd
+  echo "*** [$ANDROID_ABI][Finished] Build libvpx"
+
+  # 3
+  echo "*** [$ANDROID_ABI][Start] Build oboe"
+  pushd build/oboe
+  make clean
+  cmake -DBUILD_SHARED_LIBS=true \
+          -DCMAKE_BUILD_TYPE=RelWithDebInfo \
+          -DANDROID_TOOLCHAIN=clang \
+          -DANDROID_STL=c++_shared \
+          -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
+          -DCMAKE_INSTALL_PREFIX=$PREFIX \
+          -DANDROID_ABI=$ANDROID_ABI \
+          -DANDROID_PLATFORM=android-$API_LEVEL
+  make -j5
+  make install
+  mv $PREFIX/lib/$ANDROID_ABI/liboboe.a $PREFIX/lib/
+  popd
+  echo "*** [$ANDROID_ABI][Finished] Build oboe"
+
+  echo "*** [$ANDROID_ABI][All Finished]"
+}
 
-echo "*** [Start] Build libvpx"
 git clone -b v1.11.0 --depth=1 https://github.com/webmproject/libvpx.git build/libvpx
-pushd build/libvpx
-export NDK=$ANDROID_NDK
-export HOST_TAG=linux-x86_64
-export TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/$HOST_TAG
-export AR=$TOOLCHAIN/bin/aarch64-linux-android-ar
-export AS=$TOOLCHAIN/bin/aarch64-linux-android-as
-export CC=$TOOLCHAIN/bin/aarch64-linux-android21-clang
-export CXX=$TOOLCHAIN/bin/aarch64-linux-android21-clang++
-export LD=$TOOLCHAIN/bin/aarch64-linux-android-ld.gold  # if ndk < 22, use aarch64-linux-android-ld
-export RANLIB=$TOOLCHAIN/bin/aarch64-linux-android-ranlib
-export STRIP=$TOOLCHAIN/bin/aarch64-linux-android-strip
-
-./configure --target=arm64-android-gcc \
-            --enable-pic --disable-vp8 \
-            --disable-webm-io \
-            --disable-unit-tests \
-            --disable-examples \
-            --disable-libyuv \
-            --disable-postproc \
-            --disable-vp8 \
-            --disable-tools \
-            --disable-docs \
-            --prefix=$PREFIX
-make -j5
-make install
-
-popd
-echo "*** [Finished] Build libvpx"
-
-
-echo "*** [Start] Build oboe"
 git clone -b 1.6.1 --depth=1 https://github.com/google/oboe build/oboe
-patch -d build/oboe -p1 < ../src/oboe.patch
-pushd build/oboe
-cmake -DBUILD_SHARED_LIBS=true \
-        -DCMAKE_BUILD_TYPE=RelWithDebInfo \
-        -DANDROID_TOOLCHAIN=clang \
-        -DANDROID_STL=c++_shared \
-        -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
-        -DCMAKE_INSTALL_PREFIX=$PREFIX \
-        -DANDROID_ABI=arm64-v8a \
-        -DANDROID_PLATFORM=android-21
-make -j5
-make install
-mv $PREFIX/lib/arm64-v8a/liboboe.a $PREFIX/lib/
-popd
-echo "*** [Finished] Build oboe"
-echo "*** [All Finished]"
+patch -N -d build/oboe -p1 < ../src/oboe.patch
+
+# VCPKG_TARGET	        ANDROID_ABI
+#   arm64-android	        arm64-v8a
+#   arm-android	          armeabi-v7a
+#   x64-android	          x86_64
+#   x86-android	          x86
+
+# NDK_LLVM_TARGET
+#   aarch64-linux-android
+#   arm-linux-androideabi
+#   x86_64-linux-android
+#   i686-linux-android
+
+# LIBVPX_TARGET : 
+#   arm64-android-gcc  
+#   armv7-android-gcc  
+#   x86_64-android-gcc
+#   x86-android-gcc   
+
+# args: ANDROID_ABI  VCPKG_TARGET  NDK_LLVM_TARGET  LIBVPX_TARGET
+build arm64-v8a arm64-android aarch64-linux-android arm64-android-gcc
+build armeabi-v7a arm-android arm-linux-androideabi armv7-android-gcc 
 
 # rm -rf build/libvpx
 # rm -rf build/oboe
\ No newline at end of file
diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart
index c6a70460d..d6be51986 100644
--- a/flutter/lib/common.dart
+++ b/flutter/lib/common.dart
@@ -233,7 +233,7 @@ class AccessibilityListener extends StatelessWidget {
           }
         },
         onPointerUp: (evt) {
-          if (evt.size == 1 && GestureBinding.instance != null) {
+          if (evt.size == 1) {
             GestureBinding.instance.handlePointerEvent(PointerUpEvent(
                 pointer: evt.pointer + offset,
                 size: 0.1,
@@ -243,7 +243,7 @@ class AccessibilityListener extends StatelessWidget {
           }
         },
         onPointerMove: (evt) {
-          if (evt.size == 1 && GestureBinding.instance != null) {
+          if (evt.size == 1) {
             GestureBinding.instance.handlePointerEvent(PointerMoveEvent(
                 pointer: evt.pointer + offset,
                 size: 0.1,
diff --git a/flutter/lib/models/file_model.dart b/flutter/lib/models/file_model.dart
index 49184cf5b..e4afc892f 100644
--- a/flutter/lib/models/file_model.dart
+++ b/flutter/lib/models/file_model.dart
@@ -593,15 +593,7 @@ class FileFetcher {
 
   tryCompleteTask(String? msg, String? isLocalStr) {
     if (msg == null || isLocalStr == null) return;
-    late final isLocal;
     late final tasks;
-    if (isLocalStr == "true") {
-      isLocal = true;
-    } else if (isLocalStr == "false") {
-      isLocal = false;
-    } else {
-      return;
-    }
     try {
       final fd = FileDirectory.fromJson(jsonDecode(msg));
       if (fd.id > 0) {
diff --git a/flutter/lib/models/native_model.dart b/flutter/lib/models/native_model.dart
index f6824dda8..a600e372e 100644
--- a/flutter/lib/models/native_model.dart
+++ b/flutter/lib/models/native_model.dart
@@ -21,7 +21,6 @@ typedef F2 = Pointer<Utf8> Function(Pointer<Utf8>, Pointer<Utf8>);
 typedef F3 = void Function(Pointer<Utf8>, Pointer<Utf8>);
 
 class PlatformFFI {
-  static Pointer<RgbaFrame>? _lastRgbaFrame;
   static String _dir = '';
   static String _homeDir = '';
   static F2? _getByName;
diff --git a/flutter/lib/models/web_model.dart b/flutter/lib/models/web_model.dart
index d9668272a..3ec6c9b9f 100644
--- a/flutter/lib/models/web_model.dart
+++ b/flutter/lib/models/web_model.dart
@@ -1,3 +1,5 @@
+// ignore_for_file: avoid_web_libraries_in_flutter
+
 import 'dart:convert';
 import 'dart:typed_data';
 import 'dart:js';
diff --git a/flutter/lib/pages/connection_page.dart b/flutter/lib/pages/connection_page.dart
index 2cfbaa63b..90f290136 100644
--- a/flutter/lib/pages/connection_page.dart
+++ b/flutter/lib/pages/connection_page.dart
@@ -100,8 +100,8 @@ class _ConnectionPageState extends State<ConnectionPage> {
         : InkWell(
             onTap: () async {
               final url = _updateUrl + '.apk';
-              if (await canLaunch(url)) {
-                await launch(url);
+              if (await canLaunchUrl(Uri.parse(url))) {
+                await launchUrl(Uri.parse(url));
               }
             },
             child: Container(
diff --git a/flutter/lib/pages/remote_page.dart b/flutter/lib/pages/remote_page.dart
index 9feef9577..b93069203 100644
--- a/flutter/lib/pages/remote_page.dart
+++ b/flutter/lib/pages/remote_page.dart
@@ -126,7 +126,7 @@ class _RemotePageState extends State<RemotePage> {
           common < oldValue.length &&
               common < newValue.length &&
               newValue[common] == oldValue[common];
-          ++common);
+          ++common) {}
       for (i = 0; i < oldValue.length - common; ++i) {
         FFI.inputKey('VK_BACK');
       }
diff --git a/flutter/lib/pages/settings_page.dart b/flutter/lib/pages/settings_page.dart
index 90ff0d564..2c8b7fe9a 100644
--- a/flutter/lib/pages/settings_page.dart
+++ b/flutter/lib/pages/settings_page.dart
@@ -68,8 +68,8 @@ class _SettingsState extends State<SettingsPage> {
           tiles: [
             SettingsTile.navigation(
                 onPressed: (context) async {
-                  if (await canLaunch(url)) {
-                    await launch(url);
+                  if (await canLaunchUrl(Uri.parse(url))) {
+                    await launchUrl(Uri.parse(url));
                   }
                 },
                 title: Text(translate("Version: ") + version),
@@ -105,8 +105,8 @@ void showAbout() {
         InkWell(
             onTap: () async {
               const url = 'https://rustdesk.com/';
-              if (await canLaunch(url)) {
-                await launch(url);
+              if (await canLaunchUrl(Uri.parse(url))) {
+                await launchUrl(Uri.parse(url));
               }
             },
             child: Padding(
@@ -149,7 +149,7 @@ fetch('http://localhost:21114/api/login', {
     'uuid': FFI.getByName('uuid')
   };
   try {
-    final response = await http.post(Uri.parse('${url}/api/login'),
+    final response = await http.post(Uri.parse('$url/api/login'),
         headers: {"Content-Type": "application/json"}, body: json.encode(body));
     return parseResp(response.body);
   } catch (e) {
@@ -186,7 +186,7 @@ void refreshCurrentUser() async {
     'uuid': FFI.getByName('uuid')
   };
   try {
-    final response = await http.post(Uri.parse('${url}/api/currentUser'),
+    final response = await http.post(Uri.parse('$url/api/currentUser'),
         headers: {
           "Content-Type": "application/json",
           "Authorization": "Bearer $token"
@@ -212,7 +212,7 @@ void logout() async {
     'uuid': FFI.getByName('uuid')
   };
   try {
-    await http.post(Uri.parse('${url}/api/logout'),
+    await http.post(Uri.parse('$url/api/logout'),
         headers: {
           "Content-Type": "application/json",
           "Authorization": "Bearer $token"
@@ -242,7 +242,7 @@ String getUrl() {
           url = 'http://${tmp[0]}:$port';
         }
       } else {
-        url = 'http://${url}:21114';
+        url = 'http://$url:21114';
       }
     }
   }
diff --git a/flutter/lib/widgets/gestures.dart b/flutter/lib/widgets/gestures.dart
index d70fe05e6..960439678 100644
--- a/flutter/lib/widgets/gestures.dart
+++ b/flutter/lib/widgets/gestures.dart
@@ -594,10 +594,7 @@ class _TapTracker {
     required this.entry,
     required Duration doubleTapMinTime,
     required this.gestureSettings,
-  })  : assert(doubleTapMinTime != null),
-        assert(event != null),
-        assert(event.buttons != null),
-        pointer = event.pointer,
+  })  : pointer = event.pointer,
         _initialGlobalPosition = event.position,
         initialButtons = event.buttons,
         _doubleTapMinTimeCountdown =
@@ -643,7 +640,7 @@ class _TapTracker {
 /// CountdownZoned tracks whether the specified duration has elapsed since
 /// creation, honoring [Zone].
 class _CountdownZoned {
-  _CountdownZoned({required Duration duration}) : assert(duration != null) {
+  _CountdownZoned({required Duration duration}) {
     Timer(duration, _onTimeout);
   }
 
diff --git a/libs/hbb_common/protos/message.proto b/libs/hbb_common/protos/message.proto
index 10e04d404..61f739343 100644
--- a/libs/hbb_common/protos/message.proto
+++ b/libs/hbb_common/protos/message.proto
@@ -447,9 +447,9 @@ message PermissionInfo {
 
 enum ImageQuality {
   NotSet = 0;
-  Low = 2;
-  Balanced = 3;
-  Best = 4;
+  Low = 50;
+  Balanced = 66;
+  Best = 100;
 }
 
 message VideoCodecState {
@@ -471,7 +471,7 @@ message OptionMessage {
   BoolOption show_remote_cursor = 3;
   BoolOption privacy_mode = 4;
   BoolOption block_input = 5;
-  int32 custom_image_quality = 6;
+  uint32 custom_image_quality = 6;
   BoolOption disable_audio = 7;
   BoolOption disable_clipboard = 8;
   BoolOption enable_file_transfer = 9;
@@ -481,6 +481,8 @@ message OptionMessage {
 message TestDelay {
   int64 time = 1;
   bool from_client = 2;
+  uint32 last_delay = 3;
+  uint32 target_bitrate = 4;
 }
 
 message PublicKey {
diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs
index f01ac36de..d8810ba27 100644
--- a/libs/hbb_common/src/config.rs
+++ b/libs/hbb_common/src/config.rs
@@ -140,6 +140,8 @@ pub struct PeerConfig {
     pub disable_clipboard: bool,
     #[serde(default)]
     pub enable_file_transfer: bool,
+    #[serde(default)]
+    pub show_quality_monitor: bool,
 
     // the other scalar value must before this
     #[serde(default)]
diff --git a/libs/scrap/src/common/android.rs b/libs/scrap/src/common/android.rs
index 1975a6505..8322da3cd 100644
--- a/libs/scrap/src/common/android.rs
+++ b/libs/scrap/src/common/android.rs
@@ -3,8 +3,8 @@ use crate::rgba_to_i420;
 use lazy_static::lazy_static;
 use serde_json::Value;
 use std::collections::HashMap;
-use std::io;
 use std::sync::Mutex;
+use std::{io, time::Duration};
 
 lazy_static! {
     static ref SCREEN_SIZE: Mutex<(u16, u16, u16)> = Mutex::new((0, 0, 0)); // (width, height, scale)
@@ -33,7 +33,7 @@ impl Capturer {
         self.display.height() as usize
     }
 
-    pub fn frame<'a>(&'a mut self, _timeout_ms: u32) -> io::Result<Frame<'a>> {
+    pub fn frame<'a>(&'a mut self, _timeout: Duration) -> io::Result<Frame<'a>> {
         if let Some(buf) = get_video_raw() {
             crate::would_block_if_equal(&mut self.saved_raw_data, buf)?;
             rgba_to_i420(self.width(), self.height(), buf, &mut self.bgra);
diff --git a/libs/scrap/src/common/codec.rs b/libs/scrap/src/common/codec.rs
index bfc93fabc..f55a4bfd3 100644
--- a/libs/scrap/src/common/codec.rs
+++ b/libs/scrap/src/common/codec.rs
@@ -50,6 +50,8 @@ pub trait EncoderApi {
     fn encode_to_message(&mut self, frame: &[u8], ms: i64) -> ResultType<Message>;
 
     fn use_yuv(&self) -> bool;
+
+    fn set_bitrate(&mut self, bitrate: u32) -> ResultType<()>;
 }
 
 pub struct DecoderCfg {
diff --git a/libs/scrap/src/common/vpxcodec.rs b/libs/scrap/src/common/vpxcodec.rs
index 4067996d0..2943419e4 100644
--- a/libs/scrap/src/common/vpxcodec.rs
+++ b/libs/scrap/src/common/vpxcodec.rs
@@ -119,17 +119,6 @@ impl EncoderApi for VpxEncoder {
                 c.rc_target_bitrate = config.bitrate;
                 c.rc_undershoot_pct = 95;
                 c.rc_dropframe_thresh = 25;
-                if config.rc_min_quantizer > 0 {
-                    c.rc_min_quantizer = config.rc_min_quantizer;
-                }
-                if config.rc_max_quantizer > 0 {
-                    c.rc_max_quantizer = config.rc_max_quantizer;
-                }
-                let mut speed = config.speed;
-                if speed <= 0 {
-                    speed = 6;
-                }
-
                 c.g_threads = if config.num_threads == 0 {
                     num_cpus::get() as _
                 } else {
@@ -174,7 +163,7 @@ impl EncoderApi for VpxEncoder {
                     Higher numbers (7 or 8) will be lower quality but more manageable for lower latency
                     use cases and also for lower CPU power devices such as mobile.
                     */
-                    call_vpx!(vpx_codec_control_(&mut ctx, VP8E_SET_CPUUSED as _, speed,));
+                    call_vpx!(vpx_codec_control_(&mut ctx, VP8E_SET_CPUUSED as _, 7,));
                     // set row level multi-threading
                     /*
                     as some people in comments and below have already commented,
@@ -232,6 +221,13 @@ impl EncoderApi for VpxEncoder {
     fn use_yuv(&self) -> bool {
         true
     }
+
+    fn set_bitrate(&mut self, bitrate: u32) -> ResultType<()> {
+        let mut new_enc_cfg = unsafe { *self.ctx.config.enc.to_owned() };
+        new_enc_cfg.rc_target_bitrate = bitrate;
+        call_vpx!(vpx_codec_enc_config_set(&mut self.ctx, &new_enc_cfg));
+        return Ok(());
+    }
 }
 
 impl VpxEncoder {
@@ -336,9 +332,6 @@ pub struct VpxEncoderConfig {
     pub bitrate: c_uint,
     /// The codec
     pub codec: VpxVideoCodecId,
-    pub rc_min_quantizer: u32,
-    pub rc_max_quantizer: u32,
-    pub speed: i32,
     pub num_threads: u32,
 }
 
diff --git a/libs/scrap/src/common/x11.rs b/libs/scrap/src/common/x11.rs
index f8217e3b7..255819902 100644
--- a/libs/scrap/src/common/x11.rs
+++ b/libs/scrap/src/common/x11.rs
@@ -1,5 +1,5 @@
 use crate::x11;
-use std::{io, ops};
+use std::{io, ops, time::Duration};
 
 pub struct Capturer(x11::Capturer);
 
@@ -16,7 +16,7 @@ impl Capturer {
         self.0.display().rect().h as usize
     }
 
-    pub fn frame<'a>(&'a mut self, _timeout_ms: u32) -> io::Result<Frame<'a>> {
+    pub fn frame<'a>(&'a mut self, _timeout: Duration) -> io::Result<Frame<'a>> {
         Ok(Frame(self.0.frame()?))
     }
 }
diff --git a/src/client.rs b/src/client.rs
index 6354a1078..adeabda66 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -880,6 +880,8 @@ impl LoginConfigHandler {
             option.block_input = BoolOption::Yes.into();
         } else if name == "unblock-input" {
             option.block_input = BoolOption::No.into();
+        } else if name == "show-quality-monitor" {
+            config.show_quality_monitor = !config.show_quality_monitor;
         } else {
             let v = self.options.get(&name).is_some();
             if v {
@@ -912,15 +914,8 @@ impl LoginConfigHandler {
             n += 1;
         } else if q == "custom" {
             let config = PeerConfig::load(&self.id);
-            let mut it = config.custom_image_quality.iter();
-            let bitrate = it.next();
-            let quantizer = it.next();
-            if let Some(bitrate) = bitrate {
-                if let Some(quantizer) = quantizer {
-                    msg.custom_image_quality = bitrate << 8 | quantizer;
-                    n += 1;
-                }
-            }
+            msg.custom_image_quality = config.custom_image_quality[0] as _;
+            n += 1;
         }
         if self.get_toggle_option("show-remote-cursor") {
             msg.show_remote_cursor = BoolOption::Yes.into();
@@ -987,6 +982,8 @@ impl LoginConfigHandler {
             self.config.disable_audio
         } else if name == "disable-clipboard" {
             self.config.disable_clipboard
+        } else if name == "show-quality-monitor" {
+            self.config.show_quality_monitor
         } else {
             !self.get_option(name).is_empty()
         }
@@ -1008,17 +1005,17 @@ impl LoginConfigHandler {
         msg_out
     }
 
-    pub fn save_custom_image_quality(&mut self, bitrate: i32, quantizer: i32) -> Message {
+    pub fn save_custom_image_quality(&mut self, custom_image_quality: u32) -> Message {
         let mut misc = Misc::new();
         misc.set_option(OptionMessage {
-            custom_image_quality: bitrate << 8 | quantizer,
+            custom_image_quality,
             ..Default::default()
         });
         let mut msg_out = Message::new();
         msg_out.set_misc(misc);
         let mut config = self.load_config();
         config.image_quality = "custom".to_owned();
-        config.custom_image_quality = vec![bitrate, quantizer];
+        config.custom_image_quality = vec![custom_image_quality as _];
         self.save_config(config);
         msg_out
     }
@@ -1215,14 +1212,6 @@ where
     return (video_sender, audio_sender);
 }
 
-pub async fn handle_test_delay(t: TestDelay, peer: &mut Stream) {
-    if !t.from_client {
-        let mut msg_out = Message::new();
-        msg_out.set_test_delay(t);
-        allow_err!(peer.send(&msg_out).await);
-    }
-}
-
 // mask = buttons << 3 | type
 // type, 1: down, 2: up, 3: wheel
 // buttons, 1: left, 2: right, 4: middle
diff --git a/src/mobile.rs b/src/mobile.rs
index fe02513a0..5b9651e54 100644
--- a/src/mobile.rs
+++ b/src/mobile.rs
@@ -436,7 +436,12 @@ impl Interface for Session {
         let mut displays = Vec::new();
         let mut current = pi.current_display as usize;
 
-        if !lc.is_file_transfer {
+        if lc.is_file_transfer {
+            if pi.username.is_empty() {
+                self.msgbox("error", "Error", "No active console user logged on, please connect and logon first.");
+                return;
+            }
+        } else {
             if pi.displays.is_empty() {
                 self.msgbox("error", "Remote Error", "No Display");
             }
diff --git a/src/server/connection.rs b/src/server/connection.rs
index e09a60bb8..8f04841c2 100644
--- a/src/server/connection.rs
+++ b/src/server/connection.rs
@@ -3,6 +3,7 @@ use super::{input_service::*, *};
 use crate::clipboard_file::*;
 #[cfg(not(any(target_os = "android", target_os = "ios")))]
 use crate::common::update_clipboard;
+use crate::video_service;
 #[cfg(any(target_os = "android", target_os = "ios"))]
 use crate::{common::MOBILE_INFO2, mobile::connection_manager::start_channel};
 use crate::{ipc, VERSION};
@@ -69,7 +70,6 @@ pub struct Connection {
     audio: bool,
     file: bool,
     last_test_delay: i64,
-    image_quality: i32,
     lock_after_session_end: bool,
     show_remote_cursor: bool, // by peer
     ip: String,
@@ -105,7 +105,7 @@ impl Subscriber for ConnInner {
     }
 }
 
-const TEST_DELAY_TIMEOUT: Duration = Duration::from_secs(3);
+const TEST_DELAY_TIMEOUT: Duration = Duration::from_secs(1);
 const SEC30: Duration = Duration::from_secs(30);
 const H1: Duration = Duration::from_secs(3600);
 const MILLI1: Duration = Duration::from_millis(1);
@@ -154,7 +154,6 @@ impl Connection {
             audio: Config::get_option("enable-audio").is_empty(),
             file: Config::get_option("enable-file-transfer").is_empty(),
             last_test_delay: 0,
-            image_quality: ImageQuality::Balanced.value(),
             lock_after_session_end: false,
             show_remote_cursor: false,
             ip: "".to_owned(),
@@ -376,8 +375,11 @@ impl Connection {
                     if time > 0 && conn.last_test_delay == 0 {
                         conn.last_test_delay = time;
                         let mut msg_out = Message::new();
+                        let qos = video_service::VIDEO_QOS.lock().unwrap();
                         msg_out.set_test_delay(TestDelay{
                             time,
+                            last_delay:qos.current_delay,
+                            target_bitrate:qos.target_bitrate,
                             ..Default::default()
                         });
                         conn.inner.send(msg_out.into());
@@ -394,9 +396,8 @@ impl Connection {
             let _ = privacy_mode::turn_off_privacy(0);
         }
         video_service::notify_video_frame_feched(id, None);
-        video_service::update_test_latency(id, 0);
-        video_service::update_image_quality(id, None);
         scrap::codec::Encoder::update_video_encoder(id, scrap::codec::EncoderUpdate::Remove);
+        video_service::VIDEO_QOS.lock().unwrap().reset();
         if let Err(err) = conn.try_port_forward_loop(&mut rx_from_cm).await {
             conn.on_close(&err.to_string(), false);
         }
@@ -665,7 +666,7 @@ impl Connection {
             res.set_peer_info(pi);
         } else {
             try_activate_screen();
-            match super::video_service::get_displays() {
+            match video_service::get_displays() {
                 Err(err) => {
                     res.set_error(format!("X11 error: {}", err));
                 }
@@ -903,10 +904,11 @@ impl Connection {
                 self.inner.send(msg_out.into());
             } else {
                 self.last_test_delay = 0;
-                let latency = crate::get_time() - t.time;
-                if latency > 0 {
-                    super::video_service::update_test_latency(self.inner.id(), latency);
-                }
+                let new_delay = (crate::get_time() - t.time) as u32;
+                video_service::VIDEO_QOS
+                    .lock()
+                    .unwrap()
+                    .update_network_delay(new_delay);
             }
         } else if self.authorized {
             match msg.union {
@@ -1082,7 +1084,7 @@ impl Connection {
                 },
                 Some(message::Union::misc(misc)) => match misc.union {
                     Some(misc::Union::switch_display(s)) => {
-                        super::video_service::switch_display(s.display);
+                        video_service::switch_display(s.display);
                     }
                     Some(misc::Union::chat_message(c)) => {
                         self.send_to_cm(ipc::Data::ChatMessage { text: c.text });
@@ -1092,7 +1094,7 @@ impl Connection {
                     }
                     Some(misc::Union::refresh_video(r)) => {
                         if r {
-                            super::video_service::refresh();
+                            video_service::refresh();
                         }
                     }
                     Some(misc::Union::video_received(_)) => {
@@ -1112,13 +1114,18 @@ impl Connection {
     async fn update_option(&mut self, o: &OptionMessage) {
         log::info!("Option update: {:?}", o);
         if let Ok(q) = o.image_quality.enum_value() {
-            self.image_quality = q.value();
-            super::video_service::update_image_quality(self.inner.id(), Some(q.value()));
-        }
-        let q = o.custom_image_quality;
-        if q > 0 {
-            self.image_quality = q;
-            super::video_service::update_image_quality(self.inner.id(), Some(q));
+            let mut image_quality = None;
+            if let ImageQuality::NotSet = q {
+                if o.custom_image_quality > 0 {
+                    image_quality = Some(o.custom_image_quality);
+                }
+            } else {
+                image_quality = Some(q.value() as _)
+            }
+            video_service::VIDEO_QOS
+                .lock()
+                .unwrap()
+                .update_image_quality(image_quality);
         }
         if let Ok(q) = o.lock_after_session_end.enum_value() {
             if q != BoolOption::NotSet {
diff --git a/src/server/video_service.rs b/src/server/video_service.rs
index 62e3a5138..fa0b77fc7 100644
--- a/src/server/video_service.rs
+++ b/src/server/video_service.rs
@@ -37,19 +37,249 @@ use std::{
 use virtual_display;
 
 pub const NAME: &'static str = "video";
+const FPS: u8 = 30;
 
 lazy_static::lazy_static! {
     static ref CURRENT_DISPLAY: Arc<Mutex<usize>> = Arc::new(Mutex::new(usize::MAX));
     static ref LAST_ACTIVE: Arc<Mutex<Instant>> = Arc::new(Mutex::new(Instant::now()));
     static ref SWITCH: Arc<Mutex<bool>> = Default::default();
-    static ref TEST_LATENCIES: Arc<Mutex<HashMap<i32, i64>>> = Default::default();
-    static ref IMAGE_QUALITIES: Arc<Mutex<HashMap<i32, i32>>> = Default::default();
     static ref FRAME_FETCHED_NOTIFIER: (UnboundedSender<(i32, Option<Instant>)>, Arc<TokioMutex<UnboundedReceiver<(i32, Option<Instant>)>>>) = {
         let (tx, rx) = unbounded_channel();
         (tx, Arc::new(TokioMutex::new(rx)))
     };
     static ref PRIVACY_MODE_CONN_ID: Mutex<i32> = Mutex::new(0);
     static ref IS_CAPTURER_MAGNIFIER_SUPPORTED: bool = is_capturer_mag_supported();
+    pub static ref VIDEO_QOS: Arc<Mutex<VideoQoS>> = Default::default();
+}
+
+pub struct VideoQoS {
+    width: u32,
+    height: u32,
+    user_image_quality: u32,
+    current_image_quality: u32,
+    enable_abr: bool,
+
+    pub current_delay: u32,
+    pub fps: u8,             // abr
+    pub target_bitrate: u32, // abr
+    updated: bool,
+
+    state: AdaptiveState,
+    last_delay: u32,
+    count: u32,
+}
+
+#[derive(Debug)]
+enum AdaptiveState {
+    Normal,
+    LowDelay,
+    HeightDelay,
+}
+
+impl Default for VideoQoS {
+    fn default() -> Self {
+        VideoQoS {
+            fps: FPS,
+            user_image_quality: ImageQuality::Balanced.value() as _,
+            current_image_quality: ImageQuality::Balanced.value() as _,
+            enable_abr: false,
+            width: 0,
+            height: 0,
+            current_delay: 0,
+            target_bitrate: 0,
+            updated: false,
+            state: AdaptiveState::Normal,
+            last_delay: 0,
+            count: 0,
+        }
+    }
+}
+
+const MAX: f32 = 1.2;
+const MIN: f32 = 0.8;
+const MAX_COUNT: u32 = 3;
+const MAX_DELAY: u32 = 500;
+const MIN_DELAY: u32 = 50;
+
+impl VideoQoS {
+    pub fn set_size(&mut self, width: u32, height: u32) {
+        if width == 0 || height == 0 {
+            return;
+        }
+        self.width = width;
+        self.height = height;
+    }
+
+    pub fn spf(&mut self) -> Duration {
+        if self.fps <= 0 {
+            self.fps = FPS;
+        }
+        time::Duration::from_secs_f32(1. / (self.fps as f32))
+    }
+
+    // abr
+    pub fn update_network_delay(&mut self, delay: u32) {
+        if self.current_delay.eq(&0) {
+            self.current_delay = delay;
+            return;
+        }
+        let current_delay = self.current_delay as f32;
+
+        self.current_delay = delay / 2 + self.current_delay / 2;
+        log::debug!(
+            "update_network_delay:{}, {}, state:{:?},count:{}",
+            self.current_delay,
+            delay,
+            self.state,
+            self.count
+        );
+
+        if self.current_delay < MIN_DELAY {
+            if self.fps != 30 && self.current_image_quality != self.user_image_quality {
+                log::debug!("current_delay is normal, set to user_image_quality");
+                self.fps = 30;
+                self.current_image_quality = self.user_image_quality;
+                let _ = self.generate_bitrate().ok();
+                self.updated = true;
+            }
+            self.state = AdaptiveState::Normal;
+        } else if self.current_delay > MAX_DELAY {
+            if self.fps != 5 && self.current_image_quality != 25 {
+                log::debug!("current_delay is very height, set fps to 5, image_quality to 25");
+                self.fps = 5;
+                self.current_image_quality = 25;
+                let _ = self.generate_bitrate().ok();
+                self.updated = true;
+            }
+        } else {
+            let delay = delay as f32;
+            let last_delay = self.last_delay as f32;
+            match self.state {
+                AdaptiveState::Normal => {
+                    if delay > current_delay * MAX {
+                        self.state = AdaptiveState::HeightDelay;
+                    } else if delay < current_delay * MIN
+                        && self.current_image_quality < self.user_image_quality
+                    {
+                        self.state = AdaptiveState::LowDelay;
+                    }
+                    self.count = 1;
+                    self.last_delay = self.current_delay
+                }
+                AdaptiveState::HeightDelay => {
+                    if delay > last_delay {
+                        if self.count > MAX_COUNT {
+                            self.decrease_quality();
+                            self.reset_state();
+                            return;
+                        }
+                        self.count += 1;
+                    } else {
+                        self.reset_state();
+                    }
+                }
+                AdaptiveState::LowDelay => {
+                    if delay < last_delay * MIN {
+                        if self.count > MAX_COUNT {
+                            self.increase_quality();
+                            self.reset_state();
+                            return;
+                        }
+                        self.count += 1;
+                    } else {
+                        self.reset_state();
+                    }
+                }
+            }
+        }
+    }
+
+    fn reset_state(&mut self) {
+        self.count = 0;
+        self.state = AdaptiveState::Normal;
+    }
+
+    fn increase_quality(&mut self) {
+        log::debug!("Adaptive increase quality");
+        if self.fps < FPS {
+            log::debug!("increase fps {} -> {}", self.fps, FPS);
+            self.fps = FPS;
+        } else {
+            self.current_image_quality += self.current_image_quality / 2;
+            let _ = self.generate_bitrate().ok();
+            log::debug!("increase quality:{}", self.current_image_quality);
+        }
+        self.updated = true;
+    }
+
+    fn decrease_quality(&mut self) {
+        log::debug!("Adaptive decrease quality");
+        if self.fps < 15 {
+            log::debug!("fps is low enough :{}", self.fps);
+            return;
+        }
+        if self.current_image_quality < ImageQuality::Low.value() as _ {
+            self.fps = self.fps / 2;
+            log::debug!("decrease fps:{}", self.fps);
+        } else {
+            self.current_image_quality -= self.current_image_quality / 2;
+            let _ = self.generate_bitrate().ok();
+            log::debug!("decrease quality:{}", self.current_image_quality);
+        };
+        self.updated = true;
+    }
+
+    pub fn update_image_quality(&mut self, image_quality: Option<u32>) {
+        if let Some(image_quality) = image_quality {
+            if image_quality < 10 || image_quality > 200 {
+                self.current_image_quality = ImageQuality::Balanced.value() as _;
+            }
+            if self.current_image_quality != image_quality {
+                self.current_image_quality = image_quality;
+                let _ = self.generate_bitrate().ok();
+                self.updated = true;
+            }
+        } else {
+            self.current_image_quality = ImageQuality::Balanced.value() as _;
+        }
+        self.user_image_quality = self.current_image_quality;
+    }
+
+    pub fn generate_bitrate(&mut self) -> ResultType<u32> {
+        // https://www.nvidia.com/en-us/geforce/guides/broadcasting-guide/
+        if self.width == 0 || self.height == 0 {
+            bail!("Fail to generate_bitrate, width or height is not set");
+        }
+        if self.current_image_quality == 0 {
+            self.current_image_quality = ImageQuality::Balanced.value() as _;
+        }
+
+        let base_bitrate = ((self.width * self.height) / 800) as u32;
+
+        #[cfg(target_os = "android")]
+        {
+            // fix when andorid screen shrinks
+            let fix = Display::fix_quality() as u32;
+            log::debug!("Android screen, fix quality:{}", fix);
+            let base_bitrate = base_bitrate * fix;
+            self.target_bitrate = base_bitrate * self.image_quality / 100;
+            Ok(self.target_bitrate)
+        }
+        self.target_bitrate = base_bitrate * self.current_image_quality / 100;
+        Ok(self.target_bitrate)
+    }
+
+    pub fn check_if_updated(&mut self) -> bool {
+        if self.updated {
+            self.updated = false;
+            return true;
+        }
+        return false;
+    }
+
+    pub fn reset(&mut self) {
+        *self = Default::default();
+    }
 }
 
 fn is_capturer_mag_supported() -> bool {
@@ -129,7 +359,7 @@ impl VideoFrameController {
 }
 
 trait TraitCapturer {
-    fn frame<'a>(&'a mut self, timeout_ms: u32) -> Result<Frame<'a>>;
+    fn frame<'a>(&'a mut self, timeout: Duration) -> Result<Frame<'a>>;
 
     #[cfg(windows)]
     fn is_gdi(&self) -> bool;
@@ -138,8 +368,8 @@ trait TraitCapturer {
 }
 
 impl TraitCapturer for Capturer {
-    fn frame<'a>(&'a mut self, timeout_ms: u32) -> Result<Frame<'a>> {
-        self.frame(timeout_ms)
+    fn frame<'a>(&'a mut self, timeout: Duration) -> Result<Frame<'a>> {
+        self.frame(timeout)
     }
 
     #[cfg(windows)]
@@ -326,9 +556,6 @@ fn run(sp: GenericService) -> ResultType<()> {
     #[cfg(windows)]
     ensure_close_virtual_device()?;
 
-    let fps = 30;
-    let wait = 1000 / fps;
-    let spf = time::Duration::from_secs_f32(1. / (fps as f32));
     let (ndisplay, current, display) = get_current_display()?;
     let (origin, width, height) = (display.origin(), display.width(), display.height());
     log::debug!(
@@ -342,16 +569,21 @@ fn run(sp: GenericService) -> ResultType<()> {
         num_cpus::get(),
     );
 
-    let q = get_image_quality();
-    let (bitrate, rc_min_quantizer, rc_max_quantizer, speed) = get_quality(width, height, q);
-    log::info!("bitrate={}, rc_min_quantizer={}", bitrate, rc_min_quantizer);
+    let mut video_qos = VIDEO_QOS.lock().unwrap();
+
+    video_qos.set_size(width as _, height as _);
+    let mut spf = video_qos.spf();
+    let bitrate = video_qos.generate_bitrate()?;
+    drop(video_qos);
+
+    log::info!("init bitrate={}", bitrate);
 
     let encoder_cfg = match Encoder::current_hw_encoder_name() {
         Some(codec_name) => EncoderCfg::HW(HwEncoderConfig {
             codec_name,
             width,
             height,
-            bitrate_ratio: q >> 8,
+            bitrate_ratio: bitrate as _,
         }),
         None => EncoderCfg::VPX(VpxEncoderConfig {
             width: width as _,
@@ -359,9 +591,6 @@ fn run(sp: GenericService) -> ResultType<()> {
             timebase: [1, 1000], // Output timestamp precision
             bitrate,
             codec: VpxVideoCodecId::VP9,
-            rc_min_quantizer,
-            rc_max_quantizer,
-            speed,
             num_threads: (num_cpus::get() / 2) as _,
         }),
     };
@@ -418,10 +647,24 @@ fn run(sp: GenericService) -> ResultType<()> {
     let mut try_gdi = 1;
     #[cfg(windows)]
     log::info!("gdi: {}", c.is_gdi());
+
     while sp.ok() {
         #[cfg(windows)]
         check_uac_switch(privacy_mode_id, captuerer_privacy_mode_id)?;
 
+        {
+            let mut video_qos = VIDEO_QOS.lock().unwrap();
+            if video_qos.check_if_updated() {
+                log::debug!(
+                    "qos is updated, target_bitrate:{}, fps:{}",
+                    video_qos.target_bitrate,
+                    video_qos.fps
+                );
+                encoder.set_bitrate(video_qos.target_bitrate).unwrap();
+                spf = video_qos.spf();
+            }
+        }
+
         if *SWITCH.lock().unwrap() {
             bail!("SWITCH");
         }
@@ -430,9 +673,6 @@ fn run(sp: GenericService) -> ResultType<()> {
             bail!("SWITCH");
         }
         check_privacy_mode_changed(&sp, privacy_mode_id)?;
-        if get_image_quality() != q {
-            bail!("SWITCH");
-        }
         #[cfg(windows)]
         {
             if crate::platform::windows::desktop_changed() {
@@ -454,7 +694,7 @@ fn run(sp: GenericService) -> ResultType<()> {
         frame_controller.reset();
 
         #[cfg(any(target_os = "android", target_os = "ios"))]
-        let res = match (*c).frame(wait as _) {
+        let res = match c.frame(spf) {
             Ok(frame) => {
                 let time = now - start;
                 let ms = (time.as_secs() * 1000 + time.subsec_millis() as u64) as i64;
@@ -477,7 +717,7 @@ fn run(sp: GenericService) -> ResultType<()> {
         };
 
         #[cfg(not(any(target_os = "android", target_os = "ios")))]
-        let res = match (*c).frame(wait as _) {
+        let res = match c.frame(spf) {
             Ok(frame) => {
                 let time = now - start;
                 let ms = (time.as_secs() * 1000 + time.subsec_millis() as u64) as i64;
@@ -743,82 +983,3 @@ fn get_current_display() -> ResultType<(usize, usize, Display)> {
     }
     return Ok((n, current, displays.remove(current)));
 }
-
-#[inline]
-fn update_latency(id: i32, latency: i64, latencies: &mut HashMap<i32, i64>) {
-    if latency <= 0 {
-        latencies.remove(&id);
-    } else {
-        latencies.insert(id, latency);
-    }
-}
-
-pub fn update_test_latency(id: i32, latency: i64) {
-    update_latency(id, latency, &mut *TEST_LATENCIES.lock().unwrap());
-}
-
-fn convert_quality(q: i32) -> i32 {
-    let q = {
-        if q == ImageQuality::Balanced.value() {
-            (100 * 2 / 3, 12)
-        } else if q == ImageQuality::Low.value() {
-            (100 / 2, 18)
-        } else if q == ImageQuality::Best.value() {
-            (100, 12)
-        } else {
-            let bitrate = q >> 8 & 0xFF;
-            let quantizer = q & 0xFF;
-            (bitrate * 2, (100 - quantizer) * 36 / 100)
-        }
-    };
-    if q.0 <= 0 {
-        0
-    } else {
-        q.0 << 8 | q.1
-    }
-}
-
-pub fn update_image_quality(id: i32, q: Option<i32>) {
-    match q {
-        Some(q) => {
-            let q = convert_quality(q);
-            if q > 0 {
-                IMAGE_QUALITIES.lock().unwrap().insert(id, q);
-            } else {
-                IMAGE_QUALITIES.lock().unwrap().remove(&id);
-            }
-        }
-        None => {
-            IMAGE_QUALITIES.lock().unwrap().remove(&id);
-        }
-    }
-}
-
-fn get_image_quality() -> i32 {
-    IMAGE_QUALITIES
-        .lock()
-        .unwrap()
-        .values()
-        .min()
-        .unwrap_or(&convert_quality(ImageQuality::Balanced.value()))
-        .clone()
-}
-
-#[inline]
-fn get_quality(w: usize, h: usize, q: i32) -> (u32, u32, u32, i32) {
-    // https://www.nvidia.com/en-us/geforce/guides/broadcasting-guide/
-    let bitrate = q >> 8 & 0xFF;
-    let quantizer = q & 0xFF;
-    let b = ((w * h) / 1000) as u32;
-
-    #[cfg(target_os = "android")]
-    {
-        // fix when andorid screen shrinks
-        let fix = Display::fix_quality() as u32;
-        log::debug!("Android screen, fix quality:{}", fix);
-        let b = b * fix;
-        return (bitrate as u32 * b / 100, quantizer as _, 56, 7);
-    }
-
-    (bitrate as u32 * b / 100, quantizer as _, 56, 7)
-}
diff --git a/src/ui/header.tis b/src/ui/header.tis
index 4b2615a45..88ee8b6e6 100644
--- a/src/ui/header.tis
+++ b/src/ui/header.tis
@@ -159,6 +159,7 @@ class Header: Reactor.Component {
                 <li #custom type="image-quality"><span>{svg_checkmark}</span>{translate('Custom')}</li>
                 <div .separator />
                 <li #show-remote-cursor .toggle-option><span>{svg_checkmark}</span>{translate('Show remote cursor')}</li> 
+                <li #show-quality-monitor .toggle-option><span>{svg_checkmark}</span>{translate('Show quality monitor')}</li> 
                 {audio_enabled ? <li #disable-audio .toggle-option><span>{svg_checkmark}</span>{translate('Mute')}</li> : ""}
                 {is_win && pi.platform == 'Windows' && file_enabled ? <li #enable-file-transfer .toggle-option><span>{svg_checkmark}</span>{translate('Allow file copy and paste')}</li> : ""}
                 {keyboard_enabled && clipboard_enabled ? <li #disable-clipboard .toggle-option><span>{svg_checkmark}</span>{translate('Disable clipboard')}</li> : ""} 
@@ -315,7 +316,9 @@ class Header: Reactor.Component {
             handle_custom_image_quality();
         } else if (me.id == "privacy-mode") {
             togglePrivacyMode(me.id);
-        } else if (me.attributes.hasClass("toggle-option")) {
+        } else if (me.id == "show-quality-monitor") {
+            toggleQualityMonitor(me.id);
+        }else if (me.attributes.hasClass("toggle-option")) {
             handler.toggle_option(me.id);
             toggleMenuState();
         } else if (!me.attributes.hasClass("selected")) {
@@ -332,16 +335,13 @@ class Header: Reactor.Component {
 }
 
 function handle_custom_image_quality() {
-    var tmp = handler.get_custom_image_quality();
-    var bitrate0 = tmp[0] || 50;
-    var quantizer0 = tmp.length > 1 ? tmp[1] : 100;
+    var bitrate = handler.get_custom_image_quality()[0] / 2;
     msgbox("custom", "Custom Image Quality", "<div .form> \
-          <div><input type=\"hslider\" style=\"width: 50%\" name=\"bitrate\" max=\"100\" min=\"10\" value=\"" + bitrate0 + "\"/ buddy=\"bitrate-buddy\"><b #bitrate-buddy>x</b>% bitrate</div> \
-          <div><input type=\"hslider\" style=\"width: 50%\" name=\"quantizer\" max=\"100\" min=\"0\" value=\"" + quantizer0 + "\"/ buddy=\"quantizer-buddy\"><b #quantizer-buddy>x</b>% quantizer</div> \
+          <div><input type=\"hslider\" style=\"width: 50%\" name=\"bitrate\" max=\"100\" min=\"10\" value=\"" + bitrate + "\"/ buddy=\"bitrate-buddy\"><b #bitrate-buddy>x</b>% Bitrate</div> \
       </div>", function(res=null) {
         if (!res) return;
         if (!res.bitrate) return;
-        handler.save_custom_image_quality(res.bitrate, res.quantizer);
+        handler.save_custom_image_quality(res.bitrate * 2);
         toggleMenuState();
       });
 }
@@ -357,7 +357,7 @@ function toggleMenuState() {
     for (var el in $$(menu#display-options>li)) {
         el.attributes.toggleClass("selected", values.indexOf(el.id) >= 0);
     }
-    for (var id in ["show-remote-cursor", "disable-audio", "enable-file-transfer", "disable-clipboard", "lock-after-session-end"]) {
+    for (var id in ["show-remote-cursor", "show-quality-monitor", "disable-audio", "enable-file-transfer", "disable-clipboard", "lock-after-session-end"]) {
         var el = self.select('#' + id);
         if (el) {
             var value = handler.get_toggle_option(id);
@@ -425,6 +425,17 @@ function togglePrivacyMode(privacy_id) {
     }
 }
 
+function toggleQualityMonitor(name) {
+    var show = handler.get_toggle_option(name);
+    if (show) {
+        $(#quality-monitor).style.set{ display: "none" };
+    } else {
+        $(#quality-monitor).style.set{ display: "block" };
+    }
+    handler.toggle_option(name);
+    toggleMenuState();
+}
+
 handler.updateBlockInputState = function(input_blocked) {
     if (!input_blocked) {
         handler.toggle_option("block-input");
diff --git a/src/ui/remote.css b/src/ui/remote.css
index 617285e9c..66c5ce80f 100644
--- a/src/ui/remote.css
+++ b/src/ui/remote.css
@@ -9,6 +9,16 @@ div#video-wrapper {
     background: #212121;
 }
 
+div#quality-monitor {
+    top: 20px;
+    right: 20px;
+    background: #7571719c;
+    padding: 5px;
+    min-width: 150px;
+    color: azure;
+    border: solid azure;
+}
+
 video#handler {
     behavior: native-remote video;
     size: *;
@@ -24,7 +34,7 @@ img#cursor {
 }
 
 .goup {
-  transform: rotate(90deg);
+    transform: rotate(90deg);
 }
 
 table#remote-folder-view {
@@ -33,4 +43,4 @@ table#remote-folder-view {
 
 table#local-folder-view {
     context-menu: selector(menu#local-folder-view);
-}
+}
\ No newline at end of file
diff --git a/src/ui/remote.html b/src/ui/remote.html
index 32c1409e2..d58c3449b 100644
--- a/src/ui/remote.html
+++ b/src/ui/remote.html
@@ -1,12 +1,13 @@
 <html window-resizable window-frame="extended">
-    <head>
-        <style>
-            @import url(common.css);
-            @import url(remote.css);
-            @import url(file_transfer.css);
-            @import url(header.css);
-        </style>
-        <script type="text/tiscript">
+
+<head>
+    <style>
+        @import url(common.css);
+        @import url(remote.css);
+        @import url(file_transfer.css);
+        @import url(header.css);
+    </style>
+    <script type="text/tiscript">
             include "common.tis";
             include "msgbox.tis";
             include "remote.tis";
@@ -15,23 +16,28 @@
             include "grid.tis";
             include "header.tis";
         </script>
-    </head>
-    <header>
-        <div.window-icon role="window-icon"><icon /></div>
+</head>
+<header>
+    <div.window-icon role="window-icon">
+        <icon />
+        </div>
         <caption role="window-caption" />
         <div.window-toolbar />
         <div.window-buttons />
-    </header>
-    <body>
-        <div #video-wrapper>
-            <video #handler>
-                <div style="position: relative">
-                    <img #cursor src="in-memory:cursor" />
-                </div>
-            </video>
-        </div>
-        <div #file-transfer-wrapper>
-        </div>
-        <div #msgbox />
-    </body>
-</html>
+</header>
+
+<body>
+    <div #video-wrapper>
+        <video #handler>
+            <div #quality-monitor style="position: absolute; display: none" />
+            <div style="position: relative">
+                <img #cursor src="in-memory:cursor" />
+            </div>
+        </video>
+    </div>
+    <div #file-transfer-wrapper>
+    </div>
+    <div #msgbox />
+</body>
+
+</html>
\ No newline at end of file
diff --git a/src/ui/remote.rs b/src/ui/remote.rs
index a073b81c6..1abd4eb2a 100644
--- a/src/ui/remote.rs
+++ b/src/ui/remote.rs
@@ -2,7 +2,7 @@ use std::{
     collections::HashMap,
     ops::Deref,
     sync::{
-        atomic::{AtomicBool, Ordering},
+        atomic::{AtomicBool, AtomicUsize, Ordering},
         Arc, Mutex, RwLock,
     },
 };
@@ -223,7 +223,7 @@ impl sciter::EventHandler for Handler {
         fn get_custom_image_quality();
         fn save_view_style(String);
         fn save_image_quality(String);
-        fn save_custom_image_quality(i32, i32);
+        fn save_custom_image_quality(i32);
         fn refresh_video();
         fn get_toggle_option(String);
         fn is_privacy_mode_supported();
@@ -234,6 +234,25 @@ impl sciter::EventHandler for Handler {
     }
 }
 
+#[derive(Debug)]
+struct QualityStatus {
+    speed: String,
+    fps: i32,
+    delay: i32,
+    target_bitrate: i32,
+}
+
+impl Default for QualityStatus {
+    fn default() -> Self {
+        Self {
+            speed: Default::default(),
+            fps: -1,
+            delay: -1,
+            target_bitrate: -1,
+        }
+    }
+}
+
 impl Handler {
     pub fn new(cmd: String, id: String, args: Vec<String>) -> Self {
         let me = Self {
@@ -249,6 +268,18 @@ impl Handler {
         me
     }
 
+    fn update_quality_status(&self, status: QualityStatus) {
+        self.call2(
+            "updateQualityStatus",
+            &make_args!(
+                status.speed,
+                status.fps,
+                status.delay,
+                status.target_bitrate
+            ),
+        );
+    }
+
     fn start_keyboard_hook(&self) {
         if self.is_port_forward() || self.is_file_transfer() {
             return;
@@ -533,12 +564,12 @@ impl Handler {
         self.send(Data::Message(LoginConfigHandler::refresh()));
     }
 
-    fn save_custom_image_quality(&mut self, bitrate: i32, quantizer: i32) {
+    fn save_custom_image_quality(&mut self, custom_image_quality: i32) {
         let msg = self
             .lc
             .write()
             .unwrap()
-            .save_custom_image_quality(bitrate, quantizer);
+            .save_custom_image_quality(custom_image_quality as u32);
         self.send(Data::Message(msg));
     }
 
@@ -1296,7 +1327,10 @@ async fn io_loop(handler: Handler) {
         }
         return;
     }
-    let (video_sender, audio_sender) = start_video_audio_threads(|data: &[u8]| {
+    let frame_count = Arc::new(AtomicUsize::new(0));
+    let frame_count_cl = frame_count.clone();
+    let (video_sender, audio_sender) = start_video_audio_threads(move |data: &[u8]| {
+        frame_count_cl.fetch_add(1, Ordering::Relaxed);
         VIDEO
             .lock()
             .unwrap()
@@ -1319,6 +1353,8 @@ async fn io_loop(handler: Handler) {
         first_frame: false,
         #[cfg(windows)]
         clipboard_file_context: None,
+        data_count: Arc::new(AtomicUsize::new(0)),
+        frame_count,
     };
     remote.io_loop(&key, &token).await;
     remote.sync_jobs_status_to_local().await;
@@ -1369,6 +1405,8 @@ struct Remote {
     first_frame: bool,
     #[cfg(windows)]
     clipboard_file_context: Option<Box<CliprdrClientContext>>,
+    data_count: Arc<AtomicUsize>,
+    frame_count: Arc<AtomicUsize>,
 }
 
 impl Remote {
@@ -1394,6 +1432,8 @@ impl Remote {
                 #[cfg(windows)]
                 let mut rx_clip_client = get_rx_clip_client().lock().await;
 
+                let mut status_timer = time::interval(Duration::new(1, 0));
+
                 loop {
                     tokio::select! {
                         res = peer.next() => {
@@ -1406,6 +1446,7 @@ impl Remote {
                                     }
                                     Ok(ref bytes) => {
                                         last_recv_time = Instant::now();
+                                        self.data_count.fetch_add(bytes.len(), Ordering::Relaxed);
                                         if !self.handle_msg_from_peer(bytes, &mut peer).await {
                                             break
                                         }
@@ -1450,6 +1491,16 @@ impl Remote {
                                 self.timer = time::interval_at(Instant::now() + SEC30, SEC30);
                             }
                         }
+                        _ = status_timer.tick() => {
+                            let speed = self.data_count.swap(0, Ordering::Relaxed);
+                            let speed = format!("{:.2}kB/s", speed as f32 / 1024 as f32);
+                            let fps = self.frame_count.swap(0, Ordering::Relaxed) as _;
+                            self.handler.update_quality_status(QualityStatus {
+                                speed,
+                                fps,
+                                ..Default::default()
+                            });
+                        }
                     }
                 }
                 log::debug!("Exit io_loop of id={}", self.handler.id);
@@ -2370,7 +2421,7 @@ impl Remote {
             }
             back_notification::PrivacyModeState::OffSucceeded => {
                 self.handler
-                .msgbox("custom-nocancel", "Privacy mode", "Out privacy mode");
+                    .msgbox("custom-nocancel", "Privacy mode", "Out privacy mode");
                 self.update_privacy_mode(false);
             }
             back_notification::PrivacyModeState::OffByPeer => {
@@ -2549,7 +2600,16 @@ impl Interface for Handler {
     }
 
     async fn handle_test_delay(&mut self, t: TestDelay, peer: &mut Stream) {
-        handle_test_delay(t, peer).await;
+        if !t.from_client {
+            self.update_quality_status(QualityStatus {
+                delay: t.last_delay as _,
+                target_bitrate: t.target_bitrate as _,
+                ..Default::default()
+            });
+            let mut msg_out = Message::new();
+            msg_out.set_test_delay(t);
+            allow_err!(peer.send(&msg_out).await);
+        }
     }
 }
 
diff --git a/src/ui/remote.tis b/src/ui/remote.tis
index 0dae07664..cc5727755 100644
--- a/src/ui/remote.tis
+++ b/src/ui/remote.tis
@@ -456,6 +456,45 @@ function self.closing() {
     if (is_file_transfer || is_port_forward || size_adapted) handler.save_size(x, y, w, h);
 }
 
+var qualityMonitor;
+var qualityMonitorData = [];
+
+class QualityMonitor: Reactor.Component
+{
+    function this() {
+        qualityMonitor = this;
+        if (handler.get_toggle_option("show-quality-monitor")) {
+            $(#quality-monitor).style.set{ display: "block" };
+        }
+    }
+
+    function render() {
+        return <div >
+            <div>
+                Speed: {qualityMonitorData[0]}
+            </div>
+            <div>
+                FPS: {qualityMonitorData[1]}
+            </div>
+            <div>
+                Delay: {qualityMonitorData[2]} ms
+            </div>
+            <div>
+                Target Bitrate: {qualityMonitorData[3]}kb
+            </div>
+        </div>;
+    }
+}
+
+$(#quality-monitor).content(<QualityMonitor />);
+handler.updateQualityStatus = function(speed, fps, delay, bitrate) {
+    speed ? qualityMonitorData[0] = speed:null;
+    fps > -1 ? qualityMonitorData[1] = fps:null;
+    delay > -1 ? qualityMonitorData[2] = delay:null;
+    bitrate > -1 ? qualityMonitorData[3] = bitrate:null;
+    qualityMonitor.update();
+}
+
 handler.setPermission = function(name, enabled) {
     self.timer(60ms, function() {
     if (name == "keyboard") keyboard_enabled = enabled;