Compare commits

...

77 commits

Author SHA1 Message Date
Gaurav Ujjwal
698f01aacd Allow GitHub Actions to run on any push
Some checks are pending
Android CI / build (push) Waiting to run
2022-10-04 23:00:37 +05:30
Gaurav Ujjwal
09a4990433 Guard against null events passed to onScroll
It appears that on some devices, e1 can be null, which would result in a
crash. This is probably due to manufacturers messing with input handling
of AOSP.
2022-10-04 23:00:37 +05:30
Felix Yan
1fe3dedc52
Correct typos in app/CMakeLists.txt (#90) 2022-08-22 20:13:18 +05:30
Gaurav Ujjwal
ad6d081a6f Allow client-side cursor movement in view-only mode
With direct-touch off, panning the viewer requires cursor movement.
GitHub Issue: #89
2022-08-16 13:27:26 +05:30
Gaurav Ujjwal
64f06caa09 Improve two-finger-swipe vs scale detection 2022-08-15 23:17:10 +05:30
Gaurav Ujjwal
ead089fd5e Release v1.9.2 2022-08-08 20:41:00 +05:30
Gaurav Ujjwal
548e0241a4 Translations update from Hosted Weblate
* Translated using Weblate (Polish)
* Translated using Weblate (Chinese (Simplified))

Co-authored-by: WaldiS <sto@tutanota.de>
Co-authored-by: Eric <alchemillatruth@purelymail.com>
2022-08-08 20:23:04 +05:30
Gaurav Ujjwal
2ccbd0cbd8 Fix possible crash on opening the toolbar for the first time
AVNC always tried to open the left/start drawer when connecting for the
first time, to highlight it for the new users. But if a user changes the
toolbar alignment to 'End' before ever connecting to a server, AVNC will
crash when connecting for the first time, because there won't be any
'left' drawer.

Fix it by opening the toolbar directly.
2022-08-08 20:23:04 +05:30
Gaurav Ujjwal
bf73d24f9e Constrain aspect ratio of PiP window
Android requires the aspect ratio of PiP window to be within 1:2.39 range
in either orientation. If  aspect ratio of framebuffer was outside this
range, it would crash the app.

Also guarded against unlikely possibility of fbWidth/fbHeight being 0.
2022-08-02 16:50:03 +05:30
Gaurav Ujjwal
ee8ae23d5f Refactor connection initialization
Until now, connection was started at the very end of onCreate(). As a
result components like Dispatcher could not rely on VncViewModel's
profile during their initialization. Now initialization happen at
the beginning.

It also comes with a tiny bit of performance improvement.
Receiver thread can now start the connection early, while main thread
is still creating the UI.
2022-07-31 22:38:04 +05:30
Gaurav Ujjwal
a28149eb99 Fix default value for mouse back button preference 2022-07-31 12:06:20 +05:30
Gaurav Ujjwal
2abfd14d83 Remove temporary message about Hide Cursor option 2022-07-29 13:59:11 +05:30
Gaurav Ujjwal
31c90b4287 Release v1.9.1 2022-07-24 16:05:36 +05:30
Gaurav Ujjwal
4facebf48e Use immersive mode pref for full edge exclusion from System Gestures
Current approach won't work because Android enforces a 200dp vertical
limit, unless app is in sticky immersive mode.
2022-07-24 15:09:35 +05:30
Weblate (bot)
153f69d12c
Translations update from Hosted Weblate (#88)
* Translated using Weblate (German)
* Translated using Weblate (Polish)
* Translated using Weblate (Portuguese (Brazil))
* Translated using Weblate (Ukrainian)
* Translated using Weblate (Russian)

Co-authored-by: Pure Incompetence <inc@mailbox.org>
Co-authored-by: WaldiS <sto@tutanota.de>
Co-authored-by: mezysinc <mezysinc@mailon.site>
Co-authored-by: Andrij Mizyk <andmiz@proton.me>
Co-authored-by: Gaurav Ujjwal <gujjwal00@gmail.com>
2022-07-24 15:01:01 +05:30
Gaurav Ujjwal
9c3a0cc3dd Switch 'Full immersive mode' preference's default value to true
In future versions this will be merged  into 'Fullscreen' pref, so
turning it on by default should give us time to figure out any issues.
2022-07-24 14:01:42 +05:30
Gaurav Ujjwal
08b4c01d99 Exclude connection toolbar area from system gestures on API 29+ 2022-07-24 13:43:35 +05:30
Gaurav Ujjwal
22b071365e Update wolfSSL submodule 2022-07-20 20:28:26 +05:30
Gaurav Ujjwal
c671615025 OpenGL: Set texture wrap mode to GL_CLAMP_TO_EDGE
In OpenGL ES 2, support for sampling non-power-of-two textures in
fragment shaders is only guaranteed when wrap mode is GL_CLAMP_TO_EDGE.
Although most modern GPUs don't enforce this requirement, some devices
(like Tegra from Nvidia) do.

Related Github Issue: #85
2022-07-15 23:22:54 +05:30
Gaurav Ujjwal
1af619551a OpenGL: Use correct type for position attribute in Vertex Shader
For a_Position attribute, Frame.bind() only assign the x,y components,
but a_Position was declared as vec4.

While this generally does not cause any issue, its much better to
use the correct data type to avoid any possible misbehavior.
2022-07-15 23:12:02 +05:30
Gaurav Ujjwal
3d82e80beb OpenGL: Move program valition to onDrawFrame()
Program validation verifies the shaders against current OpenGL state.
So doing it in onSurfaceCreated() is not helpful, because uniforms
& attributes are not set at that stage and texture is not properly
initialized.

Now program is validated just before the draw command is issued.
2022-07-15 22:59:55 +05:30
Gaurav Ujjwal
b3bd9399f0 [Style] Cleanup 2022-06-02 21:28:59 +05:30
Gaurav Ujjwal
c0a56d1270 Release v1.9.0 2022-05-31 22:41:21 +05:30
Weblate (bot)
650668d8fa
Translations update from Hosted Weblate (#83)
Added new language using Weblate (German)

Translated using Weblate (Norwegian Bokmål)
Translated using Weblate (Ukrainian)
Translated using Weblate (Polish)
Translated using Weblate (Chinese (Simplified))
Translated using Weblate (Turkish)
Translated using Weblate (French)

Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Andrij Mizyk <andmizyk@gmail.com>
Co-authored-by: WaldiS <sto@tutanota.de>
Co-authored-by: Pure Incompetence <inc@mailbox.org>
Co-authored-by: GT-610 <myddz1005@163.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Nathan <bonnemainsnathan@gmail.com>
2022-05-31 22:39:38 +05:30
Gaurav Ujjwal
aa5c3e92a8 Update AndroidX Biometric library
Github Issue: #82
2022-05-26 20:19:50 +05:30
Gaurav Ujjwal
d6bf7c6189 Add preference for Natural Scrolling 2022-05-22 14:11:54 +05:30
Gaurav Ujjwal
c3292a5b90 Keep the pointer centered when Direct Touch is off
After moving the pointer, frame is moved in opposite direction to keep
the pointer centered on screen (as long as possible).
2022-05-22 14:11:31 +05:30
Gaurav Ujjwal
45b5fc4696 Disable APK splitting based on language & density
Resources in density-split APKs are not properly handled by AAB, which
causes a lot of warnings from Android.
2022-05-20 21:23:50 +05:30
Gaurav Ujjwal
e259c931a1 Add preference for screen orientation 2022-05-16 23:25:28 +05:30
Gaurav Ujjwal
890b7750b7 Update submodules 2022-05-16 22:13:35 +05:30
Gaurav Ujjwal
c4d5b7b2f8 Move custom rfbClient extensions to our own ClientEx 2022-05-16 22:08:29 +05:30
Gaurav Ujjwal
0a379a68e7 Add help for Drag gesture
The interaction of Drag gesture & Long press gesture can be confusing at first.
Now a helper message is shown for the Drag gesture.
2022-04-08 15:13:54 +05:30
Gaurav Ujjwal
41fb2e3b5f Wrap Discovery start/stop in exception handlers 2022-04-08 13:46:08 +05:30
Gaurav Ujjwal
f9f3a647de Release v1.8.1 2022-04-07 23:36:56 +05:30
Weblate (bot)
56fc2f8f3c
Translations update from Hosted Weblate (#72)
* Translated using Weblate (Persian)
* Translated using Weblate (Turkish)
* Translated using Weblate (Polish)

Co-authored-by: Eshagh Shahedany <eshagh094@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: WaldiS <sto@tutanota.de>
2022-04-07 23:29:58 +05:30
Gaurav Ujjwal
00bd2da0f6 Formatting + fix lint issues 2022-04-07 21:56:21 +05:30
Gaurav Ujjwal
0cce0c2ef8 Fix click position for mouse back press 2022-04-06 15:05:03 +05:30
Gaurav Ujjwal
999bdb2f72 Release v1.8.0 2022-03-31 10:02:52 +05:30
Weblate (bot)
c66d7e3668
Translations update from Hosted Weblate (#71)
Translated using Weblate (Persian)
Translated using Weblate (Ukrainian)
Translated using Weblate (Turkish)
Translated using Weblate (French)
Translated using Weblate (Polish)

Co-authored-by: Eshagh Shahedany <eshagh094@gmail.com>
Co-authored-by: Andrij Mizyk <andmizyk@gmail.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Nathan <bonnemainsnathan@gmail.com>
Co-authored-by: WaldiS <sto@tutanota.de>
2022-03-30 23:57:53 +05:30
Gaurav Ujjwal
b83747b1d0 Fix tests 2022-03-30 23:34:36 +05:30
Gaurav Ujjwal
ee49c2d1b4 Refactor mouse-triggered Back-key interception 2022-03-30 23:34:36 +05:30
Gaurav Ujjwal
bfd34747e8 Update libjpeg-turbo 2022-03-27 23:34:49 +05:30
Gaurav Ujjwal
ce4e838351 Add workaround for WolSSL CMake requirement 2022-03-27 23:00:02 +05:30
Gaurav Ujjwal
a5f3faedba Misc. translation updates 2022-03-27 22:47:40 +05:30
Gaurav Ujjwal
45929278ee Remove Single-tap gesture preference
Since the implementation of relative mode, only remaining use of this
pref was to disable Single-tap gesture, which I doubt anyone uses.
2022-03-27 22:24:03 +05:30
Gaurav Ujjwal
6526cb86b2 Restore option to hide remote pointer
It is now available as a global preference.
2022-03-27 22:12:51 +05:30
wang
5780d08a9d Translation updates from Weblate
Added new language using Weblate (Polish)
Added new language using Weblate (Chinese (Simplified))

Translated using Weblate (Turkish)
Translated using Weblate (Ukrainian)
Translated using Weblate (French)
Translated using Weblate (Portuguese (Brazil))
Translated using Weblate (Norwegian Bokmål)

Co-authored-by: Andrij Mizyk <andmizyk@gmail.com>
Co-authored-by: Саша Петровић <salepetronije@gmail.com>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Nathan <bonnemainsnathan@gmail.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Luiz-bro <luiznneto1@gmail.com>
Co-authored-by: wang <wwj402@sina.com>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: WaldiS <sto@tutanota.de>
2022-03-27 10:49:24 +05:30
Gaurav Ujjwal
ee28bf184d Add preference for swipe sensitivity 2022-03-26 23:40:32 +05:30
Gaurav Ujjwal
c6b28ea6f1 Implement 'pan-with-cursor' for relative action mode 2022-03-26 23:19:53 +05:30
Gaurav Ujjwal
ce2736ed20 Fix incorrect cursor rendering at (0,0) position 2022-03-26 17:47:08 +05:30
Gaurav Ujjwal
36d8b66cae Use correct range for pointer position coercion 2022-03-26 17:45:51 +05:30
Gaurav Ujjwal
f0475ccdcf Remove workaround required to sync pointer position in relative mode
As client-side cursor rendering is now supported, most servers will now
send message about the real pointer position.
2022-03-26 13:38:32 +05:30
Gaurav Ujjwal
d04e19be58 Disable Fling gesture in relative action mode.
Because 1-finger swipe is used for moving the mouse, fling gesture can be
confusing/bothering as it moves the frame instead of mouse.
2022-03-26 13:29:42 +05:30
Gaurav Ujjwal
52607e2b21 Implement client-side cursor rendering
If supported by server, client-side cursor rendering gives us significant
boost in cursor responsiveness. This also unlocks many other features:

1. Most servers will now report mouse movements done unilaterally on server.
This info will help with touchpad/relative action mode.

2. We can now add the ability to follow cursor while zoomed-in (it was possible
before, but looked really ugly).

3. It serves as a workaround for cursor-invisibility in TightVNC.
2022-03-25 22:57:23 +05:30
Gaurav Ujjwal
c64140d1b2 Native refactoring: Extract miscellaneous utilities 2022-03-25 22:57:23 +05:30
Gaurav Ujjwal
e068d89eae Native refactoring: Extract client extension 2022-03-25 22:57:23 +05:30
Gaurav Ujjwal
a518be5a7a Don't set ID for ServerProfile created during Discovery
A non-zero ID is used to differentiate between saved/non-saved profiles.
(May we should have a better way for that?)

Regression from: 2b64933fcb
2022-03-24 23:37:15 +05:30
Gaurav Ujjwal
388ccc8176 Handle the initial touch-point of scroll gestures by Stylus 2022-03-07 22:17:31 +05:30
Tuan Pham
239f993082
Add option to remap back press from mouse device (#59)
Co-authored-by: Tuan Pham <tuanp.swe@gmail.com>
2022-02-24 22:00:30 +05:30
Hosted Weblate
555ea0a483 Translations update from Hosted Weblate
Translated using Weblate (Norwegian Bokmål)
Translated using Weblate (Serbian)
Translated using Weblate (Ukrainian)
Translated using Weblate (French)

Co-authored-by: Andrij Mizyk <andmizyk@gmail.com>
Co-authored-by: Саша Петровић <salepetronije@gmail.com>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Nathan <bonnemainsnathan@gmail.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
2022-02-21 00:14:03 +05:30
Oğuz Ersen
b29710119e Translated using Weblate (Turkish) 2022-02-20 23:41:59 +05:30
Fabriel
6293385d88
New translation: Turkish (#61) 2022-02-18 22:20:10 +05:30
Gaurav Ujjwal
8c7f3b07bc Add workaround for devices where mouse right-click is treated as back-press
GitHub issue #53
2022-02-13 21:13:14 +05:30
Gaurav Ujjwal
bbda1b8e62 Assign a different App ID to CI-built APKs
It allows the APKs to be installed alongside Release version.
2022-02-03 11:39:32 +05:30
Gaurav Ujjwal
dddad7b914 Switch to 'release' builds on GitHub Actions
Because PRs cannot access GitHub Secrets, they will continue with the debug variant.
2022-02-02 22:15:11 +05:30
Gaurav Ujjwal
921601c05b Release v1.7.0 2022-02-02 17:55:11 +05:30
Gaurav Ujjwal
d180fafc5e Remove unused strings 2022-02-02 17:53:52 +05:30
Gaurav Ujjwal
5bc110479b Initialize LivePref lazily
It avoids unnecessary observer registrations.
2022-02-02 17:53:42 +05:30
Gaurav Ujjwal
e3e4e86305 Don't reset discovered servers if list is already empty
Updating discovered servers will trigger a cascade of operations.
RecyclerView will launch list-differ, all indicators will be notified,
and data-binding will be triggered.

If list is already empty, all this work is unnecessary & wasteful.
2022-01-29 19:02:58 +05:30
Gaurav Ujjwal
bfc0607870
Create FUNDING.yml 2022-01-29 19:00:26 +05:30
Weblate (bot)
3e04963715
Translations update from Hosted Weblate (#50)
Added new language using Weblate (Ukrainian)

Translated using Weblate (Serbian)
Translated using Weblate (Norwegian Bokmål)
Translated using Weblate (French)


Co-authored-by: Andrij Mizyk <andmizyk@gmail.com>
Co-authored-by: Саша Петровић <salepetronije@gmail.com>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Nathan <bonnemainsnathan@gmail.com>
2022-01-29 16:17:49 +05:30
Gaurav Ujjwal
26d303ebbd Refactor connection state
Until now, connection state was managed in VncClient. This was fine before
introduction of SSH Tunnel, but now, state in VncClient may not fully reflect
the state transitions. Moreover, most of the time we simply check whether
we are connected or not.

Now, simple boolean state is managed by VncClient, and more granular state
is managed by VncViewModel.
2022-01-28 21:38:01 +05:30
Gaurav Ujjwal
781263fc5f Stabilize rediscovery indicator
- Reimplemented from scratch, less hacky
- Preference change no longer requires app restart to take effect
- Moved the preference to Discovery section to provide more context
2022-01-25 12:56:16 +05:30
Gaurav Ujjwal
2b64933fcb Refactor Discovery
Instead of having different (and confusing) timeouts/restart settings, we now have
a single 'Autorun' mode. When enabled, we keep discovering servers as long as
HomeActivity is in foreground, and automatically stop in background.
So discovered servers won't become stale after timeout.
2022-01-25 12:56:16 +05:30
Gaurav Ujjwal
2b6441080f Remove obsolete comment
Fixes #46
[skip ci]
2022-01-16 21:29:18 +05:30
Gaurav Ujjwal
db275ce209 Re-implement Login auto-completion 2022-01-14 16:08:36 +05:30
Weblate (bot)
8bcae288e0
Translations update from Hosted Weblate (#47)
Translated using Weblate (Persian)
Translated using Weblate (French)
Translated using Weblate (Norwegian Bokmål)

Co-authored-by: Eshagh Shahedany <eshagh094@gmail.com>
Co-authored-by: Nathan <bonnemainsnathan@gmail.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
2022-01-14 14:11:21 +05:30
158 changed files with 3292 additions and 986 deletions

2
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1,2 @@
liberapay: gujjwal00
custom: ["https://paypal.me/gujjwal00"]

View file

@ -1,10 +1,6 @@
name: Android CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
on: push
jobs:
build:
@ -23,10 +19,19 @@ jobs:
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew assembleDebug
run: ./gradlew assembleCi
- name: Sign APK
env:
CI_SIGNING_KS: ${{ secrets.CI_SIGNING_KS }}
CI_SIGNING_KS_PASSWORD: ${{ secrets.CI_SIGNING_KS_PASSWORD }}
working-directory: ./app/build/outputs/apk/ci/
run: |
echo $CI_SIGNING_KS | base64 --decode > keystore.jks
$ANDROID_HOME/build-tools/30.0.3/apksigner sign --in app-ci-unsigned.apk --out app-ci.apk --ks keystore.jks --ks-pass 'env:CI_SIGNING_KS_PASSWORD'
- name: Upload APK
uses: actions/upload-artifact@v2
with:
name: dubug-apk
path: app/build/outputs/apk/debug/*.apk
name: avnc-ci
path: app/build/outputs/apk/ci/app-ci.apk

29
.github/workflows/pr.yml vendored Normal file
View file

@ -0,0 +1,29 @@
name: Android CI
on:
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v2
with:
submodules: true
- name: set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew assembleDebug
- name: Upload APK
uses: actions/upload-artifact@v2
with:
name: avnc-debug
path: app/build/outputs/apk/debug/*.apk

View file

@ -4,24 +4,6 @@
<JavaCodeStyleSettings>
<option name="JD_P_AT_EMPTY_LINES" value="false" />
</JavaCodeStyleSettings>
<JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value>
<package name="java.util" alias="false" withSubpackages="false" />
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
<package name="io.ktor" alias="false" withSubpackages="true" />
</value>
</option>
<option name="PACKAGES_IMPORT_LAYOUT">
<value>
<package name="" alias="false" withSubpackages="true" />
<package name="java" alias="false" withSubpackages="true" />
<package name="javax" alias="false" withSubpackages="true" />
<package name="kotlin" alias="false" withSubpackages="true" />
<package name="" alias="true" withSubpackages="true" />
</value>
</option>
</JetCodeStyleSettings>
<codeStyleSettings language="ObjectiveC">
<option name="RIGHT_MARGIN" value="120" />
<option name="SOFT_MARGINS" value="80,100" />

View file

@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.10)
project(NativeVNC C CXX ASM)
set(AVNC_EXTERN_DIR ${PROJECT_SOURCE_DIR}/../extern)
set(BUILD_SHARED_LIBS OFF)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build shared Libs" FORCE)
###############################################################################
# Utilities
@ -19,7 +19,7 @@ macro(avnc_check_submodule name)
endmacro()
# Requred to enable SIMD support on ARM
# Required to enable SIMD support on ARM
if (CMAKE_ANDROID_ARCH STREQUAL "arm64")
set(CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS} --target=aarch64-linux-android${ANDROID_NATIVE_API_LEVEL}")
elseif (CMAKE_ANDROID_ARCH STREQUAL "arm")
@ -53,7 +53,7 @@ set(AVNC_LIBSSL_SRC_DIR ${AVNC_EXTERN_DIR}/wolfssl)
set(AVNC_LIBSSL_BUILD_DIR ${CMAKE_BINARY_DIR}/wolfssl)
# CMake support in wolfSSl is still under development, so we have to
# manully set some flags to enable OpenSSL compatibility layer
# manually set some flags to enable OpenSSL compatibility layer
set(WOLFSSL_CRL yes)
set(WOLFSSL_DES3 yes)
add_definitions(-DOPENSSL_ALL -DOPENSSL_EXTRA -DHAVE_CRL -DHAVE_EX_DATA

View file

@ -11,8 +11,8 @@ android {
applicationId "com.gaurav.avnc"
minSdkVersion 21
targetSdkVersion 30
versionCode 8
versionName "1.6.0"
versionCode 14
versionName "1.9.2"
javaCompileOptions {
annotationProcessorOptions {
@ -42,6 +42,12 @@ android {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
ci {
initWith(release)
applicationIdSuffix '.ci'
versionNameSuffix ' (CI)'
}
}
buildFeatures {
@ -60,6 +66,15 @@ android {
path file('CMakeLists.txt')
}
}
bundle {
density {
enableSplit false
}
language {
enableSplit false
}
}
}
dependencies {
@ -68,7 +83,7 @@ dependencies {
implementation "androidx.fragment:fragment-ktx:1.3.4"
implementation "androidx.appcompat:appcompat:1.3.0"
implementation "androidx.dynamicanimation:dynamicanimation:1.0.0"
implementation "androidx.biometric:biometric-ktx:1.2.0-alpha03"
implementation "androidx.biometric:biometric-ktx:1.2.0-alpha04"
implementation "androidx.preference:preference:1.1.1"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
implementation "androidx.recyclerview:recyclerview:1.2.0"

View file

@ -22,7 +22,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import com.gaurav.avnc.*
import com.gaurav.avnc.model.ServerProfile
import com.gaurav.avnc.ui.vnc.VncActivity
import com.gaurav.avnc.vnc.Discovery
import org.hamcrest.core.AllOf.allOf
import org.junit.Assert.assertEquals
import org.junit.Before
@ -31,14 +30,13 @@ import org.junit.Test
import org.junit.runner.RunWith
/**
* For ease of testing, we directly modify the [discovery] instance.
* TODO: Find better approach to this
* TODO: Test discovery start/stop
*/
@RunWith(AndroidJUnit4::class)
class DiscoveryListTest {
@Rule
@JvmField
val activityRule = ActivityScenarioRule(HomeActivity::class.java)
private val testProfile = ServerProfile(name = "Test Profile", host = "123.123.123.123")
private fun testProfileMatcher() = allOf(
withParent(withId(R.id.discovered_rv)),
@ -46,35 +44,31 @@ class DiscoveryListTest {
hasDescendant(withText(testProfile.host)),
)
private lateinit var discovery: Discovery
@Rule
@JvmField
val activityRule = ActivityScenarioRule(HomeActivity::class.java)
private fun addTestProfile() {
// Directly modify internal list to simulate discovery
activityRule.scenario.onActivity { it.viewModel.discovery.servers.postValue(arrayListOf(testProfile)) }
onView(testProfileMatcher()).checkWillBeDisplayed()
}
@Before
fun before() {
activityRule.scenario.onActivity {
discovery = it.viewModel.discovery
discovery.stop()
}
// Stop discovery, otherwise tests will hang due to progressbar animation
// We can't even use the stop button
activityRule.scenario.onActivity { it.viewModel.stopDiscovery() }
onView(withId(R.id.pager)).perform(swipeLeft())
onView(withId(R.id.discovered_rv)).checkWithTimeout(matches(isCompletelyDisplayed()))
onView(withContentDescription(R.string.desc_discovery_btn)).checkWillBeDisplayed()
discovery.servers.postValue(arrayListOf(testProfile))
onView(testProfileMatcher()).checkWillBeDisplayed()
}
@Test
fun launchConnection() {
Intents.init()
Intents.intending(hasComponent(VncActivity::class.qualifiedName))
.respondWith(Instrumentation.ActivityResult(Activity.RESULT_OK, null))
addTestProfile()
onView(testProfileMatcher()).doClick()
Intents.intended(hasComponent(VncActivity::class.qualifiedName))
@ -85,6 +79,7 @@ class DiscoveryListTest {
@Test
fun launchProfileSave() {
addTestProfile()
onView(allOf(withParent(testProfileMatcher()), withId(R.id.save_btn))).doClick()
onView(withText(R.string.title_add_server_profile)).inRoot(isDialog()).checkIsDisplayed()
@ -94,6 +89,7 @@ class DiscoveryListTest {
@Test
fun copyHost() {
addTestProfile()
onView(testProfileMatcher()).doLongClick()
onView(withText(R.string.title_copy_host)).doClick()
@ -102,10 +98,8 @@ class DiscoveryListTest {
}
@Test
fun emptyListTest() {
discovery.servers.postValue(arrayListOf())
onView(withContentDescription(R.string.desc_discovery_btn)).checkWillBeDisplayed()
fun noServerFound() {
activityRule.scenario.onActivity { it.viewModel.discovery.servers.postValue(arrayListOf()) }
onView(withText(R.string.tip_discovery)).checkIsDisplayed()
}
}

View file

@ -45,9 +45,10 @@ class TouchHandlerTest {
instrumentation.runOnMainSync {
val viewModel = spyk(VncViewModel(ApplicationProvider.getApplicationContext()))
// Both of these needs to be mocked
// All of these needs to be mocked
every { viewModel.pref.input.mousePassthrough } returns mousePassthrough
every { viewModel.pref.input.gesture.dragEnabled } returns dragEnabled
every { viewModel.pref.input.gesture.swipeSensitivity } returns 1f
touchHandler = TouchHandler(viewModel, mockDispatcher)
}
}

View file

@ -9,7 +9,6 @@
package com.gaurav.avnc.ui.vnc
import android.content.Context
import android.content.Intent
import android.view.inputmethod.InputMethodManager
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.Espresso.onIdle
@ -36,8 +35,7 @@ class VncActivityTest {
testServer.start()
val profile = ServerProfile(host = testServer.host, port = testServer.port)
val intent = Intent(targetContext, VncActivity::class.java)
.putExtra("com.gaurav.avnc.server_profile", profile)
val intent = createVncIntent(targetContext, profile)
ActivityScenario.launch<VncActivity>(intent).use {
onView(withId(R.id.frame_view)).checkWillBeDisplayed() // Wait for connection

View file

@ -10,7 +10,6 @@ package com.gaurav.avnc.vnc
import com.gaurav.avnc.TestServer
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
@ -23,16 +22,16 @@ class VncClientTest {
override fun onCredentialRequired() = UserCredential()
override fun onFramebufferUpdated() {}
override fun onFramebufferSizeChanged(width: Int, height: Int) {}
override fun onClientStateChanged(newState: VncClient.State) {}
override fun onPointerMoved(x: Int, y: Int) {}
override fun onGotXCutText(text: String) {
cutText = text
}
}
lateinit var server: TestServer
lateinit var client: VncClient
lateinit var observer: TestObserver
private lateinit var server: TestServer
private lateinit var client: VncClient
private lateinit var observer: TestObserver
private val sampleText = "Smelly Cat"
private val sampleTextWithAccent = "Pokémon Fõbár"
@ -47,8 +46,8 @@ class VncClientTest {
private fun connect() {
server.start()
assertTrue(client.connect(server.host, server.port))
assertTrue(client.processServerMessage(10000))
client.connect(server.host, server.port)
client.processServerMessage(10000)
}

View file

@ -9,6 +9,7 @@
package com.gaurav.avnc.vnc
import android.net.Uri
import com.gaurav.avnc.model.ServerProfile
import org.junit.Assert.assertEquals
import org.junit.Test
@ -44,6 +45,7 @@ class VncUriTest {
assertEquals("foo", uri.password)
assertEquals(2, uri.securityType)
assertEquals(true, uri.viewOnly)
assertEquals(ServerProfile.CHANNEL_TCP, uri.channelType)
}
@Test
@ -55,6 +57,7 @@ class VncUriTest {
assertEquals("foo", uri.password)
assertEquals(2, uri.securityType)
assertEquals(true, uri.viewOnly)
assertEquals(ServerProfile.CHANNEL_TCP, uri.channelType)
}
@Test

View file

@ -24,6 +24,7 @@
<activity-alias
android:name=".StartupActivity"
android:exported="true"
android:targetActivity=".ui.home.SplashActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@ -33,6 +34,7 @@
<activity-alias
android:name=".UriReceiverActivity"
android:exported="true"
android:targetActivity=".ui.vnc.UriReceiverActivity">
<intent-filter>

View file

@ -0,0 +1,87 @@
/*
* Copyright (c) 2022 Gaurav Ujjwal.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* See COPYING.txt for more details.
*/
#ifndef AVNC_CLIENTEX_H
#define AVNC_CLIENTEX_H
#include <jni.h>
#include "Cursor.h"
/**
* We attach some additional data to every rfbClient.
* ClientEx is used as wrapper for this data.
*/
struct ClientEx {
// Reference to managed `VncClient`
jobject managedClient;
// Although frame width & height are maintained in rfbClient, those values
// are modified before our MallocFrameBuffer callback is triggered, and
// we cannot protect them with a mutex. So we maintain the framebuffer
// size here, protected with fbMutex.
int fbRealWidth;
int fbRealHeight;
// Cursor data used for client-side cursor rendering
Cursor *cursor;
// Protects modification to framebuffer & cursor
MUTEX(mutex);
};
const int ClientExTag = 1;
ClientEx *getClientExtension(rfbClient *client) {
return (ClientEx *) rfbClientGetClientData(client, (void *) &ClientExTag);
}
void setClientExtension(rfbClient *client, ClientEx *ex) {
rfbClientSetClientData(client, (void *) &ClientExTag, ex);
}
/**
* Returns reference to managed `VncClient` associated with given rfbClient.
*/
jobject getManagedClient(rfbClient *client) {
return getClientExtension(client)->managedClient;
}
/**
* Associate given rfbClient & managed `VncClient`.
*/
void setManagedClient(rfbClient *client, jobject managedClient) {
getClientExtension(client)->managedClient = managedClient;
}
/**
* Create new ClientEx and assign it to given client.
*/
ClientEx *assignClientExtension(rfbClient *client) {
auto ex = (ClientEx *) malloc(sizeof(ClientEx));
if (ex) {
INIT_MUTEX(ex->mutex);
ex->cursor = nullptr;
setClientExtension(client, ex);
}
return ex;
}
/**
* Free all resources related to client extension.
*/
void freeClientExtension(rfbClient *client) {
auto ex = getClientExtension(client);
if (ex) {
TINI_MUTEX(ex->mutex);
freeCursor(ex->cursor);
free(ex);
setClientExtension(client, nullptr);
}
}
#endif //AVNC_CLIENTEX_H

111
app/src/main/cpp/Cursor.h Normal file
View file

@ -0,0 +1,111 @@
/*
* Copyright (c) 2022 Gaurav Ujjwal.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* See COPYING.txt for more details.
*/
#ifndef AVNC_CURSOR_H
#define AVNC_CURSOR_H
/******************************************************************************
* Some servers (e.g TigerVNC) may not send the cursor immediately after
* connection. To provide consistent experience to users, we use a default
* cursor as fallback.
*****************************************************************************/
const uint16_t DefaultCursorWidth = 10;
const uint16_t DefaultCursorHeight = 16;
const uint16_t DefaultCursorXHot = 1;
const uint16_t DefaultCursorYHot = 1;
const uint32_t DefaultCursorBuffer[DefaultCursorWidth * DefaultCursorHeight]
= {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x00FFFFFF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x00FFFFFF, 0x00FFFFFF, 0, 0, 0, 0,
0, 0, 0, 0, 0x00FFFFFF, 0x00FFFFFF, 0x00FFFFFF, 0, 0, 0, 0, 0, 0, 0, 0x00FFFFFF, 0x00FFFFFF, 0x00FFFFFF,
0x00FFFFFF, 0, 0, 0, 0, 0, 0, 0x00FFFFFF, 0x00FFFFFF, 0x00FFFFFF, 0x00FFFFFF, 0x00FFFFFF, 0, 0, 0, 0, 0,
0x00FFFFFF, 0x00FFFFFF, 0x00FFFFFF, 0x00FFFFFF, 0x00FFFFFF, 0x00FFFFFF, 0, 0, 0, 0, 0x00FFFFFF, 0x00FFFFFF,
0x00FFFFFF, 0x00FFFFFF, 0x00FFFFFF, 0x00FFFFFF, 0x00FFFFFF, 0, 0, 0, 0x00FFFFFF, 0x00FFFFFF, 0x00FFFFFF,
0x00FFFFFF, 0x00FFFFFF, 0x00FFFFFF, 0x00FFFFFF, 0x00FFFFFF, 0, 0, 0x00FFFFFF, 0x00FFFFFF, 0x00FFFFFF,
0x00FFFFFF, 0x00FFFFFF, 0, 0, 0, 0, 0, 0x00FFFFFF, 0x00FFFFFF, 0, 0x00FFFFFF, 0x00FFFFFF, 0, 0, 0, 0, 0,
0x00FFFFFF, 0, 0, 0, 0x00FFFFFF, 0x00FFFFFF, 0, 0, 0, 0, 0, 0, 0, 0, 0x00FFFFFF, 0x00FFFFFF, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0x00FFFFFF, 0x00FFFFFF, 0, 0, 0, 0, 0, 0, 0, 0, 0x00FFFFFF, 0x00FFFFFF, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0};
const uint8_t DefaultCursorMask[DefaultCursorWidth * DefaultCursorHeight]
= {1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0,
0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1,
1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0};
/******************************************************************************
* Cursor management
*****************************************************************************/
/**
* Wrapper for cursor information.
*
* rfbClient struct does not maintain all cursor related information inside it.
* Things like xHot, yHot are passed only via the cursor shape callback.
* This wrapper holds all information necessary to render the cursor.
*/
struct Cursor {
uint8_t *buffer;
uint8_t *mask;
uint8_t *scratchBuffer; //Used during rendering
uint16_t width;
uint16_t height;
uint16_t xHot;
uint16_t yHot;
};
//Only 4-byte pixels are currently supported
const uint8_t PixelBytes = 4;
/**
* Creates a new CursorData, initialized with default cursor info.
*/
Cursor *newCursor() {
auto cursor = (Cursor *) malloc(sizeof(Cursor));
if (cursor) {
cursor->buffer = (uint8_t *) DefaultCursorBuffer;
cursor->mask = (uint8_t *) DefaultCursorMask;
cursor->scratchBuffer = (uint8_t *) malloc(DefaultCursorWidth * DefaultCursorHeight * PixelBytes);
cursor->width = DefaultCursorWidth;
cursor->height = DefaultCursorHeight;
cursor->xHot = DefaultCursorXHot;
cursor->yHot = DefaultCursorYHot;
}
return cursor;
}
void freeCursorBuffers(Cursor *cursor) {
if (cursor) {
free(cursor->scratchBuffer);
if (cursor->buffer != (uint8_t *) DefaultCursorBuffer) free(cursor->buffer);
if (cursor->mask != (uint8_t *) DefaultCursorMask) free(cursor->mask);
}
}
void freeCursor(Cursor *cursor) {
freeCursorBuffers(cursor);
free(cursor);
}
void updateCursor(Cursor *cursor, uint8_t *buffer, uint8_t *mask, uint16_t width, uint16_t height,
uint16_t xHot, uint16_t yHot) {
freeCursorBuffers(cursor);
cursor->buffer = buffer;
cursor->mask = mask;
cursor->scratchBuffer = (uint8_t *) malloc(width * height * PixelBytes);
cursor->width = width;
cursor->height = height;
cursor->xHot = xHot;
cursor->yHot = yHot;
}
#endif //AVNC_CURSOR_H

View file

@ -0,0 +1,94 @@
/*
* Copyright (c) 2022 Gaurav Ujjwal.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* See COPYING.txt for more details.
*/
#ifndef AVNC_UTILITY_H
#define AVNC_UTILITY_H
#include <stdarg.h>
#include <netdb.h>
#include <errno.h>
#include <android/log.h>
/******************************************************************************
* Utilities
*****************************************************************************/
/**
* Returns a native copy of the given jstring.
* Caller is responsible for releasing the memory.
*/
static char *getNativeStrCopy(JNIEnv *env, jstring jStr) {
const char *cStr = env->GetStringUTFChars(jStr, nullptr);
char *str = strdup(cStr);
env->ReleaseStringUTFChars(jStr, cStr);
return str;
}
/******************************************************************************
* Logging
*****************************************************************************/
const char *LOG_TAG = "NativeVnc";
void log_info(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
__android_log_vprint(ANDROID_LOG_INFO, LOG_TAG, fmt, args);
va_end(args);
}
void log_error(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
__android_log_vprint(ANDROID_LOG_ERROR, LOG_TAG, fmt, args);
va_end(args);
}
/**
* Converts given errno value to its description.
*/
static const char *errnoToStr(int e) {
// LibVNC is patched to report `getaddrinfo` errors as negative 'errno'.
// See ConnectClientToTcpAddr6WithTimeout() in sockets.c
if (e < -1000) {
return gai_strerror((-e) - 1000);
}
switch (e) {
case ENETDOWN:
case ENETRESET:
case ENETUNREACH:
case ECONNABORTED:
case EHOSTDOWN:
case EHOSTUNREACH:
case ETIMEDOUT:
case ENOMEM:
case EPROTO:
case EIO:
return strerror(e);
case ECONNREFUSED:
return "Connection refused! Server may be down or running on different port";
case ECONNRESET:
return "Connection closed by server";
case EACCES:
return "Authentication failed";
default:
// In this case we don't want to display errno description to user
// because it is more likely to be misleading (e.g. EINTR, EAGAIN).
// BUT add it to logs in case LibVNC didn't.
log_error("errnoToStr: (%d %s)", errno, strerror(errno));
return "";
}
}
#endif //AVNC_UTILITY_H

View file

@ -6,43 +6,12 @@
* See COPYING.txt for more details.
*/
#include <stdlib.h>
#include <stdarg.h>
#include <jni.h>
#include <android/log.h>
#include <GLES2/gl2.h>
#include <rfb/rfbclient.h>
#include <errno.h>
#include <netdb.h>
/******************************************************************************
* Logging
*****************************************************************************/
static const char *TAG = "NativeVnc";
struct Logger {
static void i(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
__android_log_vprint(ANDROID_LOG_INFO, TAG, fmt, args);
va_end(args);
}
static void w(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
__android_log_vprint(ANDROID_LOG_WARN, TAG, fmt, args);
va_end(args);
}
static void e(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
__android_log_vprint(ANDROID_LOG_ERROR, TAG, fmt, args);
va_end(args);
}
};
#include "ClientEx.h"
#include "Utility.h"
/******************************************************************************
@ -54,7 +23,7 @@ struct JniContext {
jclass managedCls; //Managed `VncClient` class
jmethodID cbFramebufferUpdated; //Cached reference to managed callback
JNIEnv *getEnv() {
JNIEnv *getEnv() const {
JNIEnv *env = nullptr;
if (vm != nullptr && vm->GetEnv((void **) &env, JNI_VERSION_1_6) == JNI_OK)
@ -93,80 +62,10 @@ Java_com_gaurav_avnc_vnc_VncClient_initLibrary(JNIEnv *env, jclass clazz) {
context.cbFramebufferUpdated = env->GetMethodID(clazz, "cbFinishedFrameBufferUpdate", "()V");
//TODO: Cache more method IDs so we don't have to repeatedly search them
rfbClientLog = &Logger::i;
rfbClientErr = &Logger::e;
rfbClientLog = &log_info;
rfbClientErr = &log_error;
}
/******************************************************************************
* Utilities
*****************************************************************************/
/**
* Returns a native copy of the given jstring.
* Caller is responsible for releasing the memory.
*/
static char *getNativeStrCopy(JNIEnv *env, jstring jStr) {
const char *cStr = env->GetStringUTFChars(jStr, nullptr);
char *str = strdup(cStr);
env->ReleaseStringUTFChars(jStr, cStr);
return str;
}
/**
* Returns reference to managed `VncClient` associated with given rfbClient.
*/
static jobject getManagedClient(rfbClient *client) {
return (jobject) rfbClientGetClientData(client, context.managedCls);
}
/**
* Associate given rfbClient with a managed `VncClient`.
*/
static void setManagedClient(rfbClient *client, jobject managedClient) {
rfbClientSetClientData(client, context.managedCls, managedClient);
}
/**
* Converts given errno value to its description.
*/
static const char *errnoToStr(int e) {
// LibVNC is patched to report `getaddrinfo` errors as negative 'errno'.
// See ConnectClientToTcpAddr6WithTimeout() in sockets.c
if (e < -1000) {
return gai_strerror((-e) - 1000);
}
switch (e) {
case ENETDOWN:
case ENETRESET:
case ENETUNREACH:
case ECONNABORTED:
case EHOSTDOWN:
case EHOSTUNREACH:
case ETIMEDOUT:
case ENOMEM:
case EPROTO:
case EIO:
return strerror(e);
case ECONNREFUSED:
return "Connection refused! Server may be down or running on different port";
case ECONNRESET:
return "Connection closed by server";
case EACCES:
return "Authentication failed";
default:
// In this case we don't want to display errno description to user
// because it is more likely to be misleading (e.g. EINTR, EAGAIN).
// BUT add it to logs in case LibVNC didn't.
Logger::e("errnoToStr: (%d %s)", errno, strerror(errno));
return "";
}
}
/******************************************************************************
* rfbClient Callbacks
@ -243,10 +142,10 @@ static rfbBool onHandleCursorPos(rfbClient *client, int x, int y) {
auto env = context.getEnv();
auto cls = context.managedCls;
jmethodID mid = env->GetMethodID(cls, "cbHandleCursorPos", "(II)Z");
jboolean result = env->CallBooleanMethod(obj, mid, x, y);
jmethodID mid = env->GetMethodID(cls, "cbHandleCursorPos", "(II)V");
env->CallVoidMethod(obj, mid, x, y);
return result == JNI_TRUE ? TRUE : FALSE;
return TRUE;
}
static void onFinishedFrameBufferUpdate(rfbClient *client) {
@ -262,36 +161,38 @@ static void onFinishedFrameBufferUpdate(rfbClient *client) {
*/
static rfbBool onMallocFrameBuffer(rfbClient *client) {
auto requestedSize = (uint64_t) client->width * client->height * client->format.bitsPerPixel / 8;
const auto width = client->width;
const auto height = client->height;
const auto requestedSize = (uint64_t) width * height * client->format.bitsPerPixel / 8;
if (requestedSize >= SIZE_MAX) {
rfbClientErr("CRITICAL: cannot allocate frameBuffer, requested size is too large\n");
errno = EPROTO;
return FALSE;
}
auto allocSize = (size_t) requestedSize;
auto ex = getClientExtension(client);
LOCK(client->fbMutex);
LOCK(ex->mutex);
{
if (client->frameBuffer)
free(client->frameBuffer);
client->frameBuffer = static_cast<uint8_t *>(malloc(allocSize));
if (client->frameBuffer) {
client->fbRealWidth = client->width;
client->fbRealHeight = client->height;
ex->fbRealWidth = width;
ex->fbRealHeight = height;
memset(client->frameBuffer, 0, allocSize); //Clear any garbage
} else {
client->fbRealWidth = 0;
client->fbRealHeight = 0;
ex->fbRealWidth = 0;
ex->fbRealHeight = 0;
}
}
UNLOCK(client->fbMutex);
UNLOCK(ex->mutex);
if (client->frameBuffer == nullptr) {
errno = ENOMEM;
rfbClientErr("CRITICAL: frameBuffer allocation failed\n");
return FALSE;
}
@ -301,11 +202,28 @@ static rfbBool onMallocFrameBuffer(rfbClient *client) {
auto cls = context.managedCls;
auto mid = env->GetMethodID(cls, "cbFramebufferSizeChanged", "(II)V");
env->CallVoidMethod(obj, mid, client->width, client->height);
env->CallVoidMethod(obj, mid, width, height);
return TRUE;
}
static void onGotCursorShape(rfbClient *client, int xHot, int yHot, int width, int height, int bytesPerPixel) {
auto ex = getClientExtension(client);
LOCK(ex->mutex);
//Steel buffers from rfbClient
updateCursor(ex->cursor, client->rcSource, client->rcMask, (uint16_t) width, (uint16_t) height,
(uint16_t) xHot, (uint16_t) yHot);
client->rcSource = NULL;
client->rcMask = NULL;
UNLOCK(ex->mutex);
//Fake framebuffer update to trigger rendering
onFinishedFrameBufferUpdate(client);
}
/**
* Hooks callbacks to rfbClient.
*/
@ -317,6 +235,7 @@ static void setCallbacks(rfbClient *client) {
client->HandleCursorPos = onHandleCursorPos;
client->FinishedFrameBufferUpdate = onFinishedFrameBufferUpdate;
client->MallocFrameBuffer = onMallocFrameBuffer;
client->GotCursorShape = onGotCursorShape;
}
@ -331,6 +250,9 @@ Java_com_gaurav_avnc_vnc_VncClient_nativeClientCreate(JNIEnv *env, jobject thiz)
if (client == nullptr)
return 0;
if (!assignClientExtension(client))
return 0;
setCallbacks(client);
client->canHandleNewFBSize = TRUE;
@ -353,7 +275,10 @@ Java_com_gaurav_avnc_vnc_VncClient_nativeConfigure(JNIEnv *env, jobject thiz, jl
SetClientAuthSchemes(client, auth, 1);
}
client->appData.useRemoteCursor = use_local_cursor;
if (use_local_cursor) {
client->appData.useRemoteCursor = TRUE;
getClientExtension(client)->cursor = newCursor();
}
}
extern "C"
@ -396,6 +321,7 @@ Java_com_gaurav_avnc_vnc_VncClient_nativeCleanup(JNIEnv *env, jobject thiz,
auto managedClient = getManagedClient(client);
env->DeleteGlobalRef(managedClient);
freeClientExtension(client);
rfbClientCleanup(client);
}
@ -428,7 +354,7 @@ extern "C"
JNIEXPORT jboolean JNICALL
Java_com_gaurav_avnc_vnc_VncClient_nativeSendKeyEvent(JNIEnv *env, jobject thiz, jlong client_ptr,
jlong key, jboolean is_down) {
return (jboolean) SendKeyEvent((rfbClient *) client_ptr, (uint32_t) key, is_down);
return (jboolean) SendKeyEvent((rfbClient *) client_ptr, (uint32_t) key, is_down ? TRUE : FALSE);
}
extern "C"
@ -488,20 +414,96 @@ JNIEXPORT void JNICALL
Java_com_gaurav_avnc_vnc_VncClient_nativeUploadFrameTexture(JNIEnv *env, jobject thiz,
jlong client_ptr) {
auto client = (rfbClient *) client_ptr;
auto ex = getClientExtension(client);
LOCK(client->fbMutex);
LOCK(ex->mutex);
if (client->frameBuffer) {
glTexImage2D(GL_TEXTURE_2D,
0,
GL_RGBA,
client->fbRealWidth,
client->fbRealHeight,
ex->fbRealWidth,
ex->fbRealHeight,
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
client->frameBuffer);
}
UNLOCK(client->fbMutex);
}
UNLOCK(ex->mutex);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_gaurav_avnc_vnc_VncClient_nativeUploadCursor(JNIEnv *env, jobject thiz, jlong client_ptr, jint px, jint py) {
auto client = (rfbClient *) client_ptr;
auto ex = getClientExtension(client);
auto cursor = ex->cursor;
if (!cursor)
return;
//Current algo for cursor rendering is slightly weird. Main issue is that
//glTexSubImage2D() does not perform any composition with target texture.
//So, we have to manually blend transparent/invalid pixels of the cursor
//with corresponding pixels from framebuffer. scratchBuffer is used for
//this composition.
LOCK(ex->mutex);
//Effective cursor position in framebuffer
int32_t fbCursorX = px - cursor->xHot;
int32_t fbCursorY = py - cursor->yHot;
//Rectangular portion of the framebuffer to be updated.
//Cursor can overflow outside the framebuffer if moved near the edges,
//but glTexSubImage2D() doesn't allow values outside target texture,
//so we need to only update the intersection of framebuffer & cursor.
int32_t left = -1, top = -1, right = -1, bottom = -1;
auto fb = (uint32_t *) client->frameBuffer;
auto buffer = (uint32_t *) cursor->buffer;
auto scratch = (uint32_t *) cursor->scratchBuffer;
auto mask = cursor->mask;
//Scratch buffer index
int32_t z = 0;
for (int32_t y = 0; y < cursor->height; ++y) {
for (int32_t x = 0; x < cursor->width; ++x) {
//Corresponding pixel in framebuffer
auto fbX = fbCursorX + x;
auto fbY = fbCursorY + y;
if (fbX >= 0 && fbX < ex->fbRealWidth && fbY >= 0 && fbY < ex->fbRealHeight) {
auto isValidPixel = mask[y * cursor->width + x];
if (isValidPixel)
scratch[z++] = buffer[y * cursor->width + x];
else
scratch[z++] = fb[fbY * ex->fbRealWidth + fbX];
if (left == -1 && top == -1) {
left = fbX;
top = fbY;
}
right = fbX;
bottom = fbY;
}
}
}
if (left >= 0 && top >= 0)
glTexSubImage2D(GL_TEXTURE_2D,
0,
left,
top,
right - left + 1,
bottom - top + 1,
GL_RGBA,
GL_UNSIGNED_BYTE,
scratch);
UNLOCK(ex->mutex);
}

View file

@ -0,0 +1,19 @@
/*
* Copyright (c) 2022 Gaurav Ujjwal.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* See COPYING.txt for more details.
*/
package com.gaurav.avnc.model
/**
* Simple model used by login auto-completion.
*/
data class LoginInfo(
val name: String, //Profile name
val host: String,
val username: String,
val password: String,
)

View file

@ -86,8 +86,10 @@ data class ServerProfile(
/**
* Whether the cursor should be drawn by client instead of server.
* It's value is currently ignored, and hardcoded to true.
* See [com.gaurav.avnc.viewmodel.VncViewModel.configureClient]
*/
var useLocalCursor: Boolean = false,
var useLocalCursor: Boolean = true,
/**
* Compatibility mode for key events.

View file

@ -10,8 +10,8 @@ package com.gaurav.avnc.model.db
import androidx.lifecycle.LiveData
import androidx.room.*
import com.gaurav.avnc.model.LoginInfo
import com.gaurav.avnc.model.ServerProfile
import com.gaurav.avnc.vnc.UserCredential
@Dao
interface ServerProfileDao {
@ -26,8 +26,8 @@ interface ServerProfileDao {
@Query("SELECT * FROM profiles")
fun getList(): List<ServerProfile>
@Query("SELECT username, password FROM profiles")
fun getCredentials(): LiveData<List<UserCredential>>
@Query("SELECT name, host, username, password FROM profiles")
fun getCredentials(): LiveData<List<LoginInfo>>
@Insert
fun insert(profile: ServerProfile): Long

View file

@ -59,6 +59,17 @@ class HomeActivity : AppCompatActivity() {
viewModel.discovery.servers.observe(this) { updateDiscoveryBadge(it) }
}
override fun onStart() {
super.onStart()
viewModel.autoStartDiscovery()
}
override fun onStop() {
super.onStop()
if (!isChangingConfigurations)
viewModel.autoStopDiscovery()
}
/**
* Handle drawer item selection.
*/

View file

@ -0,0 +1,73 @@
/*
* Copyright (c) 2022 Gaurav Ujjwal.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* See COPYING.txt for more details.
*/
package com.gaurav.avnc.ui.home
import android.animation.ValueAnimator
import android.content.Context
import android.util.AttributeSet
import android.view.View
import androidx.core.view.isVisible
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import com.gaurav.avnc.model.ServerProfile
/**
* [IndicatorView] highlights rediscovered servers. Main benefit is to immediately
* identify reachable servers, without checking Discovery tab.
*
* Don't know about other users, but I really like this feature.
* It complements discovery-autorun very well.
*/
class IndicatorView(context: Context, attrs: AttributeSet? = null) : View(context, attrs) {
private object Controller {
private val animatedViews = ArrayList<IndicatorView>()
private val animator = ValueAnimator.ofFloat(0F, 1F).apply {
duration = 800
repeatCount = ValueAnimator.INFINITE
repeatMode = ValueAnimator.REVERSE
addUpdateListener { animatedViews.forEach { view -> view.alpha = animatedValue as Float } }
}
fun add(view: IndicatorView) {
animatedViews.add(view)
if (!animator.isStarted) animator.start()
}
fun remove(view: IndicatorView) {
animatedViews.remove(view)
if (animatedViews.isEmpty() && animator.isStarted) animator.end()
}
}
private var profile: ServerProfile? = null
private var indicatedProfiles: LiveData<List<ServerProfile>>? = null
private val observer = Observer<List<ServerProfile>> { isVisible = it?.contains(profile) == true }
fun setup(profile: ServerProfile, indicatedProfiles: LiveData<List<ServerProfile>>) {
this.profile = profile
this.indicatedProfiles = indicatedProfiles
indicatedProfiles.removeObserver(observer)
indicatedProfiles.observeForever(observer)
}
override fun onVisibilityChanged(changedView: View, visibility: Int) {
super.onVisibilityChanged(changedView, visibility)
if (changedView == this) {
if (visibility == VISIBLE) Controller.add(this)
else Controller.remove(this)
}
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
Controller.remove(this)
indicatedProfiles?.removeObserver(observer)
}
}

View file

@ -160,7 +160,9 @@ class ProfileEditorFragment : DialogFragment() {
toolbar.setNavigationOnClickListener { dismiss() }
keyImportBtn.setOnClickListener { keyFilePicker.launch(arrayOf("*/*")) }
keyCompatModeHelpBtn.setOnClickListener {
MsgDialog.show(parentFragmentManager, R.string.title_key_compat_mode, R.string.msg_key_compat_mode_help)
MsgDialog.show(parentFragmentManager,
getString(R.string.title_key_compat_mode),
getString(R.string.msg_key_compat_mode_help))
}
// Setup initial values of some CheckBox views.
@ -175,8 +177,8 @@ class ProfileEditorFragment : DialogFragment() {
// TODO Move it to proper place
val securityTypes = mapOf(
"Default Security" to 0,
"None" to 1,
getString(R.string.title_default_security) to 0,
getString(R.string.pref_gesture_action_none) to 1,
"VncAuth" to 2,
"AnonTLS" to 18,
"VeNCrypt" to 19

View file

@ -10,8 +10,6 @@ package com.gaurav.avnc.ui.home
import android.view.*
import androidx.core.view.forEach
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.ListAdapter
@ -24,14 +22,10 @@ import com.gaurav.avnc.databinding.ServerSavedBinding
import com.gaurav.avnc.databinding.ServerSavedItemBinding
import com.gaurav.avnc.model.ServerProfile
import com.gaurav.avnc.ui.home.ServerTabs.PagerAdapter.ViewHolder
import com.gaurav.avnc.util.Experimental
import com.gaurav.avnc.viewmodel.HomeViewModel
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
/**
* This class creates and manages tabs in [HomeActivity].
@ -121,11 +115,6 @@ class ServerTabs(val activity: HomeActivity) {
binding.serversRv.setHasFixedSize(true)
activity.viewModel.serverProfiles.observe(activity) { adapter.submitList(it) }
if (activity.viewModel.pref.experimental.indicator) {
Experimental.Indicator().setup(binding, adapter)
}
return binding.root
}
@ -136,9 +125,6 @@ class ServerTabs(val activity: HomeActivity) {
class SavedServerAdapter(val viewModel: HomeViewModel)
: ListAdapter<ServerProfile, SavedServerAdapter.ViewHolder>(Differ) {
//Experimental feature
var bindListener: ((ViewHolder, ServerProfile) -> Unit)? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = ServerSavedItemBinding.inflate(inflater, parent, false)
@ -149,7 +135,7 @@ class ServerTabs(val activity: HomeActivity) {
val profile = getItem(position)
holder.profile = profile
holder.binding.viewModel = profile
bindListener?.invoke(holder, profile)
holder.binding.indicator.setup(profile, viewModel.rediscoveredProfiles)
}
inner class ViewHolder(val binding: ServerSavedItemBinding)
@ -177,27 +163,10 @@ class ServerTabs(val activity: HomeActivity) {
binding.discoveredRv.setHasFixedSize(true)
activity.viewModel.discovery.servers.observe(activity) { adapter.submitList(it) }
launchDiscoveryRestarter()
return binding.root
}
private fun launchDiscoveryRestarter() {
activity.lifecycleScope.launch(Dispatchers.Main) {
val viewModel = activity.viewModel
while (true) {
delay(viewModel.pref.server.discoveryTimeout + viewModel.pref.server.discoveryRestartDelay)
if (activity.lifecycle.currentState == Lifecycle.State.RESUMED &&
viewModel.pref.server.discoveryAutoStart &&
viewModel.pref.server.discoveryRestart) {
viewModel.startDiscovery()
}
}
}
}
class DiscoveredServerAdapter(val viewModel: HomeViewModel)
: ListAdapter<ServerProfile, DiscoveredServerAdapter.ViewHolder>(Differ) {

View file

@ -10,7 +10,12 @@ package com.gaurav.avnc.ui.prefs
import android.content.Context
import android.util.AttributeSet
import android.widget.ImageButton
import androidx.fragment.app.FragmentActivity
import androidx.preference.ListPreference
import androidx.preference.PreferenceViewHolder
import com.gaurav.avnc.R
import com.gaurav.avnc.util.MsgDialog
/**
* List preference with some extra features.
@ -27,4 +32,26 @@ class ListPreferenceEx(context: Context, attrs: AttributeSet) : ListPreference(c
return disabledStateSummary
return super.getSummary()
}
/**
* Message shown in a dialog, when help button of the preference is clicked.
* This will only work if [R.layout.help_btn] is used as widget layout.
*/
var helpMessage: CharSequence? = null
override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder)
(holder.findViewById(R.id.help_btn) as? ImageButton)?.setOnClickListener { showHelp() }
}
private fun showHelp() {
helpMessage?.let { helpMessage ->
(context as? FragmentActivity)?.let { fragmentActivity ->
MsgDialog.show(fragmentActivity.supportFragmentManager,
fragmentActivity.getString(R.string.desc_help_btn),
helpMessage)
}
}
}
}

View file

@ -8,11 +8,13 @@
package com.gaurav.avnc.ui.prefs
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import androidx.annotation.Keep
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference
@ -93,6 +95,8 @@ class PrefsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPreference
}
@Keep class Input : PrefFragment(R.xml.pref_input) {
private var naturalScrollingUpdater: OnSharedPreferenceChangeListener? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -105,9 +109,22 @@ class PrefsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPreference
}
}
findPreference<ListPreferenceEx>("gesture_swipe1")!!.apply {
disabledStateSummary = getString(R.string.pref_gesture_action_move_pointer)
val swipe1 = findPreference<ListPreferenceEx>("gesture_swipe1")!!
val swipe2 = findPreference<ListPreference>("gesture_swipe2")!!
val drag = findPreference<ListPreferenceEx>("gesture_drag")!!
swipe1.apply { disabledStateSummary = getString(R.string.pref_gesture_action_move_pointer) }
drag.apply { helpMessage = getText(R.string.msg_drag_gesture_help) }
// To reduce clutter & avoid 'UI overload', Natural scrolling pref is not visible by default.
// It becomes visible only when 'Scroll remote content' option is used.
naturalScrollingUpdater = OnSharedPreferenceChangeListener { _, _ ->
val pref = findPreference<SwitchPreference>("natural_scrolling")
pref?.isVisible = "remote-scroll" in listOf(swipe1.value, swipe2.value, drag.value)
}
naturalScrollingUpdater?.onSharedPreferenceChanged(null, null) //Initial update
swipe1.sharedPreferences.registerOnSharedPreferenceChangeListener(naturalScrollingUpdater)
}
}

View file

@ -9,19 +9,16 @@
package com.gaurav.avnc.ui.vnc
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.util.ArrayMap
import android.widget.ArrayAdapter
import android.widget.TextView
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
import com.gaurav.avnc.R
import com.gaurav.avnc.databinding.FragmentCredentialBinding
import com.gaurav.avnc.model.LoginInfo
import com.gaurav.avnc.viewmodel.VncViewModel
import com.gaurav.avnc.vnc.UserCredential
import com.gaurav.avnc.vnc.VncClient
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputLayout
@ -53,9 +50,10 @@ class CredentialFragment : DialogFragment() {
.setTitle(R.string.title_credentials_required)
.setView(binding.root)
.setPositiveButton(android.R.string.ok) { _, _ ->
val cred = UserCredential(binding.username.text.toString(),
binding.password.text.toString())
val username = binding.username.text.toString()
val password = getRealPassword(binding.password.text.toString())
val cred = UserCredential(username, password)
viewModel.credentialRequest.offerResponse(cred)
if (binding.remember.isChecked)
@ -80,8 +78,8 @@ class CredentialFragment : DialogFragment() {
*/
private fun scheduleCredentialSave(viewModel: VncViewModel, cred: UserCredential) {
with(viewModel) {
clientState.observeForever {
if (it == VncClient.State.Connected) {
state.observeForever {
if (it == VncViewModel.State.Connected) {
profile.username = cred.username
profile.password = cred.password
saveProfile()
@ -106,11 +104,11 @@ class CredentialFragment : DialogFragment() {
}
viewModel.knownCredentials.observe(this) { list ->
val usernames = list.map { it.username }.filter { it.isNotBlank() }.distinct()
val passwords = list.map { it.password }.filter { it.isNotBlank() }.distinct()
val usernames = list.map { it.username }.filter { it.isNotEmpty() }.distinct()
val passwords = preparePasswordSuggestions(list)
val usernameAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_list_item_1, usernames)
val passwordAdapter = PasswordAdapter(requireContext(), android.R.layout.simple_list_item_1, passwords)
val passwordAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_list_item_1, passwords)
binding.username.setAdapter(usernameAdapter)
binding.password.setAdapter(passwordAdapter)
@ -118,21 +116,20 @@ class CredentialFragment : DialogFragment() {
}
/**
* Specialized adapter for password completion. We don't want to show plaintext password
* in completion list, so we partially obfuscate it.
*
* @param layout Must point to a resource with [TextView] as root
* Instead of showing plaintext passwords, we show server name & host in suggestion
* list. When user taps OK, we convert the suggestion back to real password.
*/
private class PasswordAdapter(context: Context, layout: Int, passwords: List<String>)
: ArrayAdapter<String>(context, layout, passwords) {
private val passwordMap = ArrayMap<String, String>()
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view = super.getView(position, convertView, parent) as TextView
private fun preparePasswordSuggestions(list: List<LoginInfo>): List<String> {
list.filter { it.password.isNotEmpty() }
.map { Pair("from: ${it.name} [${it.host}]", it.password) }
.distinct()
.toMap(passwordMap)
.removeAll(passwordMap.values) //Guard against (very unlikely) clash with real password
val obfuscated = "**********" + view.text.takeLast(2)
view.text = obfuscated
return view
}
return passwordMap.keys.toList()
}
private fun getRealPassword(typedPassword: String) = passwordMap[typedPassword] ?: typedPassword
}

View file

@ -13,7 +13,6 @@ import com.gaurav.avnc.ui.vnc.Dispatcher.SwipeAction
import com.gaurav.avnc.viewmodel.VncViewModel
import com.gaurav.avnc.vnc.Messenger
import com.gaurav.avnc.vnc.PointerButton
import com.gaurav.avnc.vnc.VncClient
import kotlin.math.abs
/**
@ -62,7 +61,7 @@ class Dispatcher(private val activity: VncActivity) {
* Action configuration
**************************************************************************/
private val directMode = DirectMode()
private val relativeMode by lazy { RelativeMode() } //Lazy to avoid pointer sync behaviour
private val relativeMode = RelativeMode()
private val defaultMode = if (gesturePref.directTouch) directMode else relativeMode
private val tap1Action = selectPointAction(gesturePref.tap1)
@ -74,6 +73,8 @@ class Dispatcher(private val activity: VncActivity) {
private val swipe2Action = selectSwipeAction(gesturePref.swipe2)
private val dragAction = selectSwipeAction(gesturePref.drag)
private val mouseBackAction = selectPointAction(viewModel.pref.input.mouseBack)
private fun selectPointAction(actionName: String): (PointF) -> Unit {
return when (actionName) {
"left-click" -> { p -> defaultMode.doClick(PointerButton.Left, p) }
@ -112,8 +113,8 @@ class Dispatcher(private val activity: VncActivity) {
* Event receivers
**************************************************************************/
fun onGestureStart() = stopFling()
fun onGestureStop(p: PointF) = defaultMode.doButtonRelease(p)
fun onGestureStart() = defaultMode.onGestureStart()
fun onGestureStop(p: PointF) = defaultMode.onGestureStop(p)
fun onTap1(p: PointF) = tap1Action(p)
fun onTap2(p: PointF) = tap2Action(p)
@ -125,12 +126,13 @@ class Dispatcher(private val activity: VncActivity) {
fun onDrag(sp: PointF, cp: PointF, dx: Float, dy: Float) = dragAction(sp, cp, dx, dy)
fun onScale(scaleFactor: Float, fx: Float, fy: Float) = doScale(scaleFactor, fx, fy)
fun onFling(vx: Float, vy: Float) = doFling(vx, vy)
fun onFling(vx: Float, vy: Float) = defaultMode.doFling(vx, vy)
fun onMouseButtonDown(button: PointerButton, p: PointF) = directMode.doButtonDown(button, p)
fun onMouseButtonUp(button: PointerButton, p: PointF) = directMode.doButtonUp(button, p)
fun onMouseMove(p: PointF) = directMode.doMovePointer(p, 0f, 0f)
fun onMouseScroll(p: PointF, hs: Float, vs: Float) = directMode.doRemoteScrollFromMouse(p, hs, vs)
fun onMouseBack(p: PointF) = mouseBackAction(p)
fun onStylusTap(p: PointF) = directMode.doClick(PointerButton.Left, p)
fun onStylusDoubleTap(p: PointF) = directMode.doDoubleClick(PointerButton.Left, p)
@ -148,9 +150,6 @@ class Dispatcher(private val activity: VncActivity) {
private fun doScale(scaleFactor: Float, fx: Float, fy: Float) = viewModel.updateZoom(scaleFactor, fx, fy)
private fun doPan(dx: Float, dy: Float) = viewModel.panFrame(dx, dy)
private fun doFling(vx: Float, vy: Float) = viewModel.frameScroller.fling(vx, vy)
private fun stopFling() = viewModel.frameScroller.stop()
/**
* Most actions have the same implementation in both modes, only difference being
* the point where event is sent. [transformPoint] is used for this mode-specific
@ -161,10 +160,15 @@ class Dispatcher(private val activity: VncActivity) {
private var accumulatedDx = 0F
private var accumulatedDy = 0F
private val deltaPerScroll = 20F //For how much dx/dy, one scroll event will be sent
private val yScrollDirection = (if (gesturePref.naturalScrolling) 1 else -1)
abstract fun transformPoint(p: PointF): PointF?
abstract fun doMovePointer(p: PointF, dx: Float, dy: Float)
abstract fun doDrag(p: PointF, dx: Float, dy: Float)
abstract fun doFling(vx: Float, vy: Float)
open fun onGestureStart() {}
open fun onGestureStop(p: PointF) = doButtonRelease(p)
fun doButtonDown(button: PointerButton, p: PointF) {
transformPoint(p)?.let { messenger.sendPointerButtonDown(button, it) }
@ -190,7 +194,7 @@ class Dispatcher(private val activity: VncActivity) {
fun doRemoteScroll(focus: PointF, dx: Float, dy: Float) {
accumulatedDx += dx
accumulatedDy += dy
accumulatedDy += dy * yScrollDirection
//Drain horizontal change
while (abs(accumulatedDx) >= deltaPerScroll) {
@ -232,6 +236,8 @@ class Dispatcher(private val activity: VncActivity) {
override fun transformPoint(p: PointF) = viewModel.frameState.toFb(p)
override fun doMovePointer(p: PointF, dx: Float, dy: Float) = doButtonDown(PointerButton.None, p)
override fun doDrag(p: PointF, dx: Float, dy: Float) = doButtonDown(PointerButton.Left, p)
override fun doFling(vx: Float, vy: Float) = viewModel.frameScroller.fling(vx, vy)
override fun onGestureStart() = viewModel.frameScroller.stop()
}
/**
@ -240,15 +246,36 @@ class Dispatcher(private val activity: VncActivity) {
private inner class RelativeMode : AbstractMode() {
private val pointerPosition = PointF(0f, 0f)
override fun onGestureStart() {
super.onGestureStart()
//Initialize with the latest pointer position
pointerPosition.apply {
x = viewModel.client.pointerX.toFloat()
y = viewModel.client.pointerY.toFloat()
}
viewModel.client.ignorePointerMovesByServer = true
}
override fun onGestureStop(p: PointF) {
super.onGestureStop(p)
viewModel.client.ignorePointerMovesByServer = false
}
override fun transformPoint(p: PointF) = pointerPosition
override fun doMovePointer(p: PointF, dx: Float, dy: Float) {
pointerPosition.apply {
offset(dx, dy)
x = x.coerceIn(0f, viewModel.frameState.fbWidth)
y = y.coerceIn(0f, viewModel.frameState.fbHeight)
x = x.coerceIn(0f, viewModel.frameState.fbWidth - 1)
y = y.coerceIn(0f, viewModel.frameState.fbHeight - 1)
}
doButtonDown(PointerButton.None, pointerPosition)
//Try to keep the pointer centered on screen
val vp = viewModel.frameState.toVP(pointerPosition)
val centerDiffX = viewModel.frameState.vpWidth / 2 - vp.x
val centerDiffY = viewModel.frameState.vpHeight / 2 - vp.y
viewModel.panFrame(centerDiffX, centerDiffY)
}
override fun doDrag(p: PointF, dx: Float, dy: Float) {
@ -256,46 +283,6 @@ class Dispatcher(private val activity: VncActivity) {
doMovePointer(p, dx, dy)
}
/**
* As we have no way know the real pointer position (at-least for now), we move
* the remote pointer to center of the screen. This is a crude way of syncing
* [pointerPosition] with remote pointer position.
*
* We have to start somewhere, and center of the screen seems most appropriate.
* This is done immediately after connecting (and whenever framebuffer size changes),
* If we waited for a gesture before doing this, there would be a sudden jump,
* right before the gesture. Another "benefit" is, if connection is fast enough,
* user might not even notice our trickery.
*
* One caveat is, we can sync only after client initialization, but size change event
* is likely to fire during initialization. Another problem is activity restart,
* which will re-trigger our client state observer. We use [syncPending] to solve
* both of these problems.
*
* We can (hopefully) implement a better solution in the future.
*/
private var syncPending = false
init {
viewModel.fbSizeChangedEvent.observe(activity) {
syncPending = true
syncPosition()
}
viewModel.clientState.observe(activity) {
syncPosition()
}
}
private fun syncPosition() {
if (syncPending && viewModel.clientState.value == VncClient.State.Connected) {
pointerPosition.apply {
x = viewModel.frameState.fbWidth / 2
y = viewModel.frameState.fbHeight / 2
}
doButtonDown(PointerButton.None, pointerPosition)
syncPending = false
}
}
override fun doFling(vx: Float, vy: Float) {} //Disabled
}
}

View file

@ -194,6 +194,14 @@ class FrameState(private val minZoomScale: Float = 0.5F, private val maxZoomScal
return null
}
/**
* Converts given framebuffer point to corresponding point in viewport.
*/
fun toVP(fbPoint: PointF): PointF {
return PointF(fbPoint.x * scale + frameX, fbPoint.y * scale + frameY)
}
private fun calculateBaseScale() {
if (fbHeight == 0F || fbWidth == 0F || windowHeight == 0F)
return //Not enough info yet

View file

@ -13,6 +13,9 @@ import android.os.Build
import android.view.*
import com.gaurav.avnc.viewmodel.VncViewModel
import com.gaurav.avnc.vnc.PointerButton
import kotlin.math.PI
import kotlin.math.abs
import kotlin.math.atan2
import kotlin.math.max
/**
@ -44,7 +47,8 @@ class TouchHandler(private val viewModel: VncViewModel, private val dispatcher:
fun onHoverEvent(event: MotionEvent): Boolean {
if (event.actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
dispatcher.onMouseMove(event.point())
lastHoverPoint = event.point()
dispatcher.onMouseMove(lastHoverPoint)
return true
}
return false
@ -58,6 +62,11 @@ class TouchHandler(private val viewModel: VncViewModel, private val dispatcher:
}
}
// Used for back-press interception
private var lastHoverPoint = PointF()
fun onMouseBack() {
dispatcher.onMouseBack(lastHoverPoint)
}
/****************************************************************************************
* Mouse
@ -114,7 +123,12 @@ class TouchHandler(private val viewModel: VncViewModel, private val dispatcher:
}
inner class StylusGestureListener : GestureDetector.SimpleOnGestureListener() {
override fun onDown(e: MotionEvent?) = true
private var scrolling = false
override fun onDown(e: MotionEvent?): Boolean {
scrolling = false
return true
}
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
dispatcher.onStylusTap(e.point())
@ -133,8 +147,16 @@ class TouchHandler(private val viewModel: VncViewModel, private val dispatcher:
override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
// Scrolling with stylus button pressed is currently used for scale gesture
if (e2.buttonState and MotionEvent.BUTTON_STYLUS_PRIMARY == 0)
if (e2.buttonState and MotionEvent.BUTTON_STYLUS_PRIMARY == 0) {
// When scrolling starts, we need to send the first event at initial touch-point.
// Otherwise, we will loose the small distance (touch-slope) required by onScroll().
if (!scrolling) {
scrolling = true
dispatcher.onStylusScroll(e1.point())
}
dispatcher.onStylusScroll(e2.point())
}
return true
}
}
@ -151,10 +173,13 @@ class TouchHandler(private val viewModel: VncViewModel, private val dispatcher:
private val gestureDetector = GestureDetector(viewModel.app, this)
private val multiFingerTapDetector = MultiFingerTapDetector()
private val dragDetector = DragDetector()
private val swipeVsScale = SwipeVsScale()
private val dragEnabled = viewModel.pref.input.gesture.dragEnabled
private val swipeSensitivity = viewModel.pref.input.gesture.swipeSensitivity
private fun handleGestureEvent(event: MotionEvent): Boolean {
swipeVsScale.onTouchEvent(event)
dragDetector.onTouchEvent(event)
multiFingerTapDetector.onTouchEvent(event)
scaleDetector.onTouchEvent(event)
@ -164,7 +189,8 @@ class TouchHandler(private val viewModel: VncViewModel, private val dispatcher:
override fun onScaleBegin(detector: ScaleGestureDetector) = true
override fun onScaleEnd(detector: ScaleGestureDetector) {}
override fun onScale(detector: ScaleGestureDetector): Boolean {
dispatcher.onScale(detector.scaleFactor, detector.focusX, detector.focusY)
if (swipeVsScale.shouldScale())
dispatcher.onScale(detector.scaleFactor, detector.focusX, detector.focusY)
return true
}
@ -194,13 +220,17 @@ class TouchHandler(private val viewModel: VncViewModel, private val dispatcher:
return true
}
override fun onScroll(e1: MotionEvent, e2: MotionEvent, dX: Float, dY: Float): Boolean {
val startPoint = e1.point()
override fun onScroll(e1: MotionEvent?, e2: MotionEvent, dX: Float, dY: Float): Boolean {
// As weird as it may be, e1 can be null on some devices
val startPoint = e1?.point() ?: return false
val currentPoint = e2.point()
val normalizedDx = -dX * swipeSensitivity
val normalizedDy = -dY * swipeSensitivity
when (e2.pointerCount) {
1 -> dispatcher.onSwipe1(startPoint, currentPoint, -dX, -dY)
2 -> dispatcher.onSwipe2(startPoint, currentPoint, -dX, -dY)
1 -> dispatcher.onSwipe1(startPoint, currentPoint, normalizedDx, normalizedDy)
2 -> if (swipeVsScale.shouldSwipe())
dispatcher.onSwipe2(startPoint, currentPoint, normalizedDx, normalizedDy)
}
multiFingerTapDetector.reset()
@ -320,4 +350,106 @@ class TouchHandler(private val viewModel: VncViewModel, private val dispatcher:
fingerCount = 0
}
}
/**
* Swipe vs Scale detector.
*
* Many two-finger gestures are detected as both swipe & scale gestures because
* [GestureDetector] & [ScaleGestureDetector] work independently. This works
* very well when two-finger swipe pref is set to 'pan' (default value).
* But when the pref is set to 'remote-scroll', this independent detection
* becomes an issue. When user tries to scale, it frequently triggers remote
* scrolling. And when user tries to scroll, it triggers scaling.
*
* This class tries to clearly differentiate between these two gestures.
* It works by tracking the fingers, and calculating the angle between two paths.
* Then [decide] between two gestures by comparing the angle to some thresholds.
*
* This class can mis-detect some gestures because fingers don't always move
* perfectly, but it does provide huge improvement over existing situation.
*/
private inner class SwipeVsScale {
private val enabled = viewModel.pref.input.gesture.swipe2 == "remote-scroll"
private var detecting = false
private var scaleDetected = false
private var swipeDetected = false
private var f1Id = 0 // Finger 1
private var f2Id = 0 // Finger 2
private val f1Start = PointF()
private val f2Start = PointF()
private val f1Current = PointF()
private val f2Current = PointF()
fun onTouchEvent(e: MotionEvent) {
if (!enabled)
return
when (e.actionMasked) {
MotionEvent.ACTION_DOWN,
MotionEvent.ACTION_CANCEL,
MotionEvent.ACTION_UP,
MotionEvent.ACTION_POINTER_UP -> {
detecting = false
scaleDetected = false
swipeDetected = false
}
MotionEvent.ACTION_POINTER_DOWN -> {
detecting = e.pointerCount == 2
if (detecting) {
f1Id = e.getPointerId(0)
f2Id = e.getPointerId(1)
f1Start.set(e.getX(f1Id), e.getY(f1Id))
f2Start.set(e.getX(f2Id), e.getY(f2Id))
f1Current.set(f1Start)
f2Current.set(f2Start)
}
}
MotionEvent.ACTION_MOVE -> {
if (detecting) {
f1Current.set(e.getX(f1Id), e.getY(f1Id))
f2Current.set(e.getX(f2Id), e.getY(f2Id))
}
}
}
}
fun shouldScale(): Boolean {
decide()
return !enabled || (detecting && scaleDetected)
}
fun shouldSwipe(): Boolean {
decide()
return !enabled || (detecting && swipeDetected)
}
/**
* Decides if gesture can be considered a swipe/scale
*/
private fun decide() {
if (!detecting)
return
val t1 = theta(f1Start, f1Current)
val t2 = theta(f2Start, f2Current)
val diff = abs(t1 - t2)
scaleDetected = diff > 45
swipeDetected = diff < 30
}
/**
* Returns the angle made by line [p1]->[p2] with the positive x-axis.
* Returned angle will be in range [0, 360]
*/
private fun theta(p1: PointF, p2: PointF): Double {
val theta = atan2(p2.y - p1.y, p2.x - p1.x)
val degree = (theta / PI) * 180
return (degree + 360) % 360 // Map [-180, 180] to [0, 360]
}
}
}

View file

@ -12,16 +12,14 @@ import android.app.Activity
import android.app.PictureInPictureParams
import android.content.Context
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.graphics.Rect
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.util.Rational
import android.view.Gravity
import android.view.KeyEvent
import android.view.View
import android.view.WindowManager
import android.view.*
import android.view.inputmethod.InputMethodManager
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
@ -35,7 +33,6 @@ import com.gaurav.avnc.model.ServerProfile
import com.gaurav.avnc.util.Experimental
import com.gaurav.avnc.util.SamsungDex
import com.gaurav.avnc.viewmodel.VncViewModel
import com.gaurav.avnc.vnc.VncClient
import com.gaurav.avnc.vnc.VncUri
import kotlinx.coroutines.delay
import java.lang.ref.WeakReference
@ -44,10 +41,14 @@ import java.lang.ref.WeakReference
private const val PROFILE_KEY = "com.gaurav.avnc.server_profile"
fun createVncIntent(context: Context, profile: ServerProfile): Intent {
return Intent(context, VncActivity::class.java).apply {
putExtra(PROFILE_KEY, profile)
}
}
fun startVncActivity(source: Activity, profile: ServerProfile) {
val intent = Intent(source, VncActivity::class.java)
intent.putExtra(PROFILE_KEY, profile)
source.startActivity(intent)
source.startActivity(createVncIntent(source, profile))
}
fun startVncActivity(source: Activity, uri: VncUri) {
@ -62,7 +63,7 @@ fun startVncActivity(source: Activity, uri: VncUri) {
*/
class VncActivity : AppCompatActivity() {
private val profile by lazy { loadProfile() }
private lateinit var profile: ServerProfile
val viewModel by viewModels<VncViewModel>()
lateinit var binding: ActivityVncBinding
private val dispatcher by lazy { Dispatcher(this) }
@ -72,27 +73,29 @@ class VncActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
profile = loadProfile()
viewModel.initConnection(profile)
//Main UI
binding = DataBindingUtil.setContentView(this, R.layout.activity_vnc)
binding.viewModel = viewModel
binding.lifecycleOwner = this
binding.frameView.initialize(this)
viewModel.frameViewRef = WeakReference(binding.frameView)
setupLayout()
binding.frameView.initialize(this)
binding.retryConnectionBtn.setOnClickListener { retryConnection() }
//Drawers
setupDrawerLayout()
//Buttons
binding.keyboardBtn.setOnClickListener { showKeyboard(); closeDrawers() }
binding.zoomResetBtn.setOnClickListener { viewModel.resetZoom(); closeDrawers() }
binding.virtualKeysBtn.setOnClickListener { virtualKeys.show(); closeDrawers() }
binding.retryConnectionBtn.setOnClickListener { retryConnection() }
//ViewModel setup
viewModel.frameViewRef = WeakReference(binding.frameView)
//Observers
viewModel.credentialRequest.observe(this) { showCredentialDialog() }
viewModel.sshHostKeyVerifyRequest.observe(this) { showHostKeyDialog() }
viewModel.clientState.observe(this) { onClientStateChanged(it) }
viewModel.initConnection(profile) //Should be called after observers has been setup
viewModel.state.observe(this) { onClientStateChanged(it) }
}
override fun onResume() {
@ -119,14 +122,11 @@ class VncActivity : AppCompatActivity() {
private fun loadProfile(): ServerProfile {
val profile = intent.getParcelableExtra<ServerProfile>(PROFILE_KEY)
if (profile != null) {
// Make a copy to avoid modifying intent's instance,
// because we may need the original if we have to retry connection.
return profile.copy()
}
check(profile != null) { "ServerProfile is missing from VncActivity Intent" }
Log.e(javaClass.simpleName, "No connection information was passed through Intent.")
return ServerProfile()
// Make a copy to avoid modifying intent's instance,
// because we may need the original if we have to retry connection.
return profile.copy()
}
private fun retryConnection() {
@ -157,17 +157,17 @@ class VncActivity : AppCompatActivity() {
private fun closeDrawers() = binding.drawerLayout.closeDrawers()
private fun onClientStateChanged(newState: VncClient.State) {
if (newState == VncClient.State.Connected) {
private fun onClientStateChanged(newState: VncViewModel.State) {
if (newState == VncViewModel.State.Connected) {
if (!viewModel.pref.runInfo.hasConnectedSuccessfully) {
viewModel.pref.runInfo.hasConnectedSuccessfully = true
// Highlight drawer for first time users
binding.drawerLayout.open()
binding.drawerLayout.openDrawer(binding.primaryToolbar)
lifecycleScope.launchWhenCreated {
delay(1500)
binding.drawerLayout.close()
binding.drawerLayout.closeDrawer(binding.primaryToolbar)
}
}
@ -188,6 +188,8 @@ class VncActivity : AppCompatActivity() {
private fun setupLayout() {
setupOrientation()
@Suppress("DEPRECATION")
if (fullscreenMode) {
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
@ -227,6 +229,14 @@ class VncActivity : AppCompatActivity() {
}
}
private fun setupOrientation() {
requestedOrientation = when (viewModel.pref.viewer.orientation) {
"portrait" -> ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT
"landscape" -> ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
else -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
}
}
private fun setupDrawerLayout() {
binding.drawerLayout.setScrimColor(0)
@ -239,6 +249,25 @@ class VncActivity : AppCompatActivity() {
if (viewModel.pref.experimental.swipeCloseToolbar)
Experimental.setupDrawerCloseOnScrimSwipe(binding.drawerLayout, gravityH)
//Add System Gesture exclusion rects to allow opening toolbar drawer by swiping from edge
if (Build.VERSION.SDK_INT >= 29) {
binding.primaryToolbar.addOnLayoutChangeListener { _, left, top, right, bottom, _, _, _, _ ->
val root = binding.drawerLayout
val rect = Rect(left, top, right, bottom)
if (left < 0) rect.offset(-left, 0)
if (right > root.width) rect.offset(-(right - root.width), 0)
if (immersiveMode) {
rect.top = 0
rect.bottom = root.height
}
//Set exclusion rects on root because toolbar may not be visible
root.systemGestureExclusionRects = listOf(rect)
}
}
}
@ -254,7 +283,7 @@ class VncActivity : AppCompatActivity() {
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
window.decorView.apply {
if (viewModel.clientState.value == VncClient.State.Connected)
if (viewModel.client.connected)
systemUiVisibility = systemUiVisibility or flags
else
systemUiVisibility = systemUiVisibility and flags.inv()
@ -286,12 +315,18 @@ class VncActivity : AppCompatActivity() {
}
private fun enterPiPMode() {
val canEnter = viewModel.pref.viewer.pipEnabled && viewModel.client.state == VncClient.State.Connected
val canEnter = viewModel.pref.viewer.pipEnabled && viewModel.client.connected
if (canEnter && Build.VERSION.SDK_INT >= 26) {
val fs = viewModel.frameState
val aspectRatio = Rational(fs.fbWidth.toInt(), fs.fbHeight.toInt())
var w = viewModel.frameState.fbWidth
var h = viewModel.frameState.fbHeight
// Android require aspect ratio to be less than 2.39
w = w.coerceIn(1f, 2.3f * h)
h = h.coerceIn(1f, 2.3f * w)
val aspectRatio = Rational(w.toInt(), h.toInt())
val param = PictureInPictureParams.Builder().setAspectRatio(aspectRatio).build()
try {
@ -307,14 +342,31 @@ class VncActivity : AppCompatActivity() {
************************************************************************************/
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
return keyHandler.onKeyEvent(event) || super.onKeyDown(keyCode, event)
return keyHandler.onKeyEvent(event) || workarounds(event) || super.onKeyDown(keyCode, event)
}
override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
return keyHandler.onKeyEvent(event) || super.onKeyUp(keyCode, event)
return keyHandler.onKeyEvent(event) || workarounds(event) || super.onKeyUp(keyCode, event)
}
override fun onKeyMultiple(keyCode: Int, repeatCount: Int, event: KeyEvent): Boolean {
return keyHandler.onKeyEvent(event) || super.onKeyMultiple(keyCode, repeatCount, event)
}
private fun workarounds(keyEvent: KeyEvent): Boolean {
//It seems that some device manufacturers are hell-bent on making developers'
//life miserable. In their infinite wisdom, they decided that Android apps don't
//need Mouse right-click events. It is hardcoded to act as back-press, without
//giving apps a chance to handle it. For better or worse, they set the 'source'
//for such key events to Mouse, enabling the following workarounds.
if (keyEvent.keyCode == KeyEvent.KEYCODE_BACK &&
InputDevice.getDevice(keyEvent.deviceId).supportsSource(InputDevice.SOURCE_MOUSE) &&
viewModel.pref.input.interceptMouseBack) {
if (keyEvent.action == KeyEvent.ACTION_DOWN)
touchHandler.onMouseBack()
return true
}
return false
}
}

View file

@ -10,6 +10,7 @@ package com.gaurav.avnc.ui.vnc.gl
import android.opengl.GLES20.*
import android.util.Log
import com.gaurav.avnc.BuildConfig
/**
* Represents the GL program used for Frame rendering.
@ -34,6 +35,7 @@ class FrameProgram {
val uProjectionLocation = glGetUniformLocation(program, U_PROJECTION)
val uTexUnitLocation = glGetUniformLocation(program, U_TEXTURE_UNIT)
val textureId = createTexture()
var validated = false
fun setUniforms(projectionMatrix: FloatArray) {
@ -54,10 +56,19 @@ class FrameProgram {
glBindTexture(GL_TEXTURE_2D, texturesObjects[0])
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
glBindTexture(GL_TEXTURE_2D, 0)
return texturesObjects[0]
}
fun validate() {
if (BuildConfig.DEBUG && !validated) {
ShaderCompiler.validateProgram(program)
validated = true
}
}
fun useProgram() {
glUseProgram(program)
}

View file

@ -12,7 +12,6 @@ import android.opengl.GLES20.*
import android.opengl.GLSurfaceView
import android.opengl.Matrix
import com.gaurav.avnc.viewmodel.VncViewModel
import com.gaurav.avnc.vnc.VncClient
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.opengles.GL10
@ -23,6 +22,7 @@ class Renderer(val viewModel: VncViewModel) : GLSurfaceView.Renderer {
private val projectionMatrix = FloatArray(16)
private val state = viewModel.frameState
private val hideCursor = viewModel.pref.input.hideRemoteCursor
private lateinit var program: FrameProgram
private lateinit var frame: Frame
@ -72,7 +72,7 @@ class Renderer(val viewModel: VncViewModel) : GLSurfaceView.Renderer {
override fun onDrawFrame(gl: GL10?) {
glClear(GL_COLOR_BUFFER_BIT)
if (viewModel.client.state != VncClient.State.Connected)
if (!viewModel.client.connected)
return
if (state.vpWidth == 0f || state.vpHeight == 0f)
@ -87,9 +87,12 @@ class Renderer(val viewModel: VncViewModel) : GLSurfaceView.Renderer {
program.setUniforms(projectionMatrix)
viewModel.client.uploadFrameTexture()
if (!hideCursor) viewModel.client.uploadCursor()
frame.updateFbSize(state.fbWidth, state.fbHeight)
frame.bind(program)
program.validate()
frame.draw()
}
}

View file

@ -10,7 +10,6 @@ package com.gaurav.avnc.ui.vnc.gl
import android.opengl.GLES20.*
import android.util.Log
import com.gaurav.avnc.BuildConfig
object ShaderCompiler {
@ -76,9 +75,6 @@ object ShaderCompiler {
if (vertexShaderId == 0 || fragmentShaderId == 0)
return 0
val programId = linkProgram(vertexShaderId, fragmentShaderId)
if (BuildConfig.DEBUG)
validateProgram(programId)
return programId
return linkProgram(vertexShaderId, fragmentShaderId)
}
}

View file

@ -15,13 +15,13 @@ object Shaders {
//language=GLSL
const val VERTEX_SHADER = """
uniform mat4 u_Projection;
attribute vec4 a_Position;
attribute vec2 a_Position;
attribute vec2 a_TextureCoordinates;
varying vec2 v_TextureCoordinates;
void main()
{
v_TextureCoordinates = a_TextureCoordinates;
gl_Position = u_Projection * a_Position;
gl_Position = u_Projection * vec4(a_Position, 0, 1);
}"""
//language=GLSL

View file

@ -15,8 +15,6 @@ import androidx.preference.PreferenceManager
/**
* Utility class for accessing app preferences
*
* - All timeouts are in milliseconds.
*/
class AppPreferences(context: Context) {
@ -29,6 +27,7 @@ class AppPreferences(context: Context) {
}
inner class Viewer {
val orientation; get() = prefs.getString("viewer_orientation", "auto")
val fullscreen; get() = prefs.getBoolean("fullscreen_display", true)
val pipEnabled; get() = prefs.getBoolean("pip_enabled", false)
val toolbarAlignment; get() = prefs.getString("toolbar_alignment", "start")
@ -38,7 +37,7 @@ class AppPreferences(context: Context) {
inner class Gesture {
val directTouch; get() = prefs.getBoolean("gesture_direct_touch", true)
val tap1; get() = prefs.getString("gesture_single_tap", "left-click")!!
val tap1 = "left-click" //Preference UI was removed
val tap2; get() = prefs.getString("gesture_tap2", "open-keyboard")!!
val doubleTap; get() = prefs.getString("gesture_double_tap", "double-click")!!
val longPress; get() = prefs.getString("gesture_long_press", "right-click")!!
@ -46,6 +45,8 @@ class AppPreferences(context: Context) {
val swipe2; get() = prefs.getString("gesture_swipe2", "pan")!!
val drag; get() = prefs.getString("gesture_drag", "none")!!
val dragEnabled; get() = (drag != "none")
val swipeSensitivity; get() = prefs.getInt("gesture_swipe_sensitivity", 10) / 10f
val naturalScrolling; get() = prefs.getBoolean("natural_scrolling", true)
}
inner class Input {
@ -56,6 +57,9 @@ class AppPreferences(context: Context) {
val mousePassthrough; get() = prefs.getBoolean("mouse_passthrough", true)
val hideLocalCursor; get() = prefs.getBoolean("hide_local_cursor", false)
val hideRemoteCursor; get() = prefs.getBoolean("hide_remote_cursor", false)
val mouseBack; get() = prefs.getString("mouse_back", "right-click")!!
val interceptMouseBack; get() = mouseBack != "default"
val kmLanguageSwitchToSuper; get() = prefs.getBoolean("km_language_switch_to_super", false)
val kmRightAltToSuper; get() = prefs.getBoolean("km_right_alt_to_super", false)
@ -63,19 +67,15 @@ class AppPreferences(context: Context) {
}
inner class Server {
val discoveryTimeout; get() = prefs.getInt("discovery_timeout", 10) * 1000L
val discoveryAutoStart; get() = prefs.getBoolean("discovery_auto_start", true)
val discoveryRestart; get() = prefs.getBoolean("discovery_restart", false)
val discoveryRestartDelay; get() = prefs.getInt("discovery_restart_delay", 5) * 60 * 1000L
val credAutocomplete; get() = prefs.getBoolean("cred_autocomplete", true)
val clipboardSync; get() = prefs.getBoolean("clipboard_sync", true)
val discoveryAutorun; get() = prefs.getBoolean("discovery_autorun", true)
val rediscoveryIndicator = LivePref("rediscovery_indicator", true)
}
inner class Experimental {
val indicator; get() = prefs.getBoolean("experimental_indicator", false)
val swipeCloseToolbar; get() = prefs.getBoolean("experimental_vnc_toolbar_swipe_close", true)
val immersiveMode; get() = prefs.getBoolean("viewer_immersive_mode", false)
val immersiveMode; get() = prefs.getBoolean("viewer_immersive_mode", true)
}
/**
@ -110,9 +110,14 @@ class AppPreferences(context: Context) {
updateValue()
}
init {
updateValue() //Initial load
prefs.registerOnSharedPreferenceChangeListener(prefChangeListener)
private var initialized = false
override fun onActive() {
if (!initialized) {
initialized = true
updateValue()
prefs.registerOnSharedPreferenceChangeListener(prefChangeListener)
}
}
private fun updateValue() {

View file

@ -8,148 +8,18 @@
package com.gaurav.avnc.util
import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.util.Log
import android.view.GestureDetector
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
import androidx.annotation.Keep
import androidx.drawerlayout.widget.DrawerLayout
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import androidx.recyclerview.widget.RecyclerView
import com.gaurav.avnc.databinding.ServerSavedBinding
import com.gaurav.avnc.databinding.ServerSavedItemBinding
import com.gaurav.avnc.model.ServerProfile
import com.gaurav.avnc.ui.home.ServerTabs.SavedServerAdapter
/**
* All the experimental stuff is dumped here.
*/
object Experimental {
/**
* This feature allows us to show indicator (small pulsating circle) for already
* saved profiles if we find the same server in Discovery. Main benefit is to
* immediately know about reachable servers.
*
* Why Experimental:
* - For this to be most useful the user needs to run Discovery frequently.
* Otherwise, indicators will become stale.
* - Because of Discovery & animation we will consume slightly more battery.
*
* One other approach for this implementation is to have a field in [ServerProfile],
* update that field in [com.gaurav.avnc.viewmodel.HomeViewModel] and let Data Binding
* automatically render the indicator. But that approach has some disadvantages:
*
* - It complicates code in other classes too much.
* - Because each Indicator view has an independent animator, they pulse out of sync.
* - I simply don't like that approach.
*/
class Indicator {
/**
* Local reference to discovered servers.
*/
private var discoveredServers: List<ServerProfile>? = null
/**
* Maps each [ServerSavedItemBinding.indicator] view to the server profile being
* rendered by its container.
*/
private val profileMap = HashMap<View, ServerProfile>(5)
/**
* Maps each [ServerSavedItemBinding.indicator] view to its availability state.
* If state is true, the view will be animated otherwise it will be hidden.
*/
private val animatorMap = HashMap<View, Boolean>(5)
/**
* Animator for 'alpha' property of indicator views.
*/
private val animator = ValueAnimator.ofFloat(0F, 1F).apply {
duration = 1000
repeatCount = ValueAnimator.INFINITE
repeatMode = ValueAnimator.REVERSE
addUpdateListener { onAnimationUpdate() }
}
/**
* Initialize Indicator support.
*
* Note: Viewmodel & lifecycle must have been assigned to [binding].
*/
fun setup(binding: ServerSavedBinding, adapter: SavedServerAdapter) {
Log.i(javaClass.simpleName, "Setting up Experimental Indicator")
// There are 4 event sources which we are interested in:
// 1 - Discovery: Need to know which items should show indicator.
binding.viewModel!!.discovery.servers.observe(binding.lifecycleOwner!!) {
discoveredServers = it
for ((v, p) in profileMap)
animatorMap[v] = isServerDiscovered(p)
updateAnimator()
}
// 2 - RV Binding: We need to know which RV Item View is rendering which profile.
adapter.bindListener = { holder, profile ->
val view = holder.binding.indicator
profileMap[view] = profile
animatorMap[view] = isServerDiscovered(profile)
}
// 3 - RV Views: We need to clear Views which are no longer used by RecyclerView.
binding.serversRv.addOnChildAttachStateChangeListener(object : RecyclerView.OnChildAttachStateChangeListener {
override fun onChildViewAttachedToWindow(view: View) {}
override fun onChildViewDetachedFromWindow(view: View) {
profileMap.remove(view)
animatorMap.remove(view)
}
})
// 4 - Activity Lifecycle: We don't want to run animation if our activity is not in front.
binding.lifecycleOwner!!.lifecycle.addObserver(object : LifecycleObserver {
@Keep
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onStart() = updateAnimator()
@Keep
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onStop() = animator.end()
})
}
private fun isServerDiscovered(p: ServerProfile): Boolean {
return discoveredServers?.find { p.host == it.host && p.port == it.port } != null
}
private fun updateAnimator() {
if (discoveredServers?.isNotEmpty() == true) {
if (!animator.isStarted)
animator.start()
} else
animator.end()
}
private fun onAnimationUpdate() {
for ((v, animate) in animatorMap) {
if (animate) {
v.alpha = animator.animatedValue as Float
v.visibility = View.VISIBLE
} else {
v.visibility = View.GONE
}
}
}
}
/**
* Normally, drawers in [DrawerLayout] are closed by two gestures:

View file

@ -2,7 +2,6 @@ package com.gaurav.avnc.util
import android.app.Dialog
import android.os.Bundle
import androidx.annotation.StringRes
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -13,12 +12,12 @@ object MsgDialog {
/**
* Shows a dialog with given title & message,
*/
fun show(manager: FragmentManager, @StringRes titleRes: Int, @StringRes msgRes: Int) {
fun show(manager: FragmentManager, title: CharSequence, msg: CharSequence) {
val fragment = MsgDialogFragment()
val args = Bundle(2)
args.putInt("titleRes", titleRes)
args.putInt("msgRes", msgRes)
args.putCharSequence("title", title)
args.putCharSequence("msg", msg)
fragment.arguments = args
fragment.show(manager, null)
@ -27,8 +26,8 @@ object MsgDialog {
class MsgDialogFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return MaterialAlertDialogBuilder(requireContext())
.setTitle(requireArguments().getInt("titleRes"))
.setMessage(requireArguments().getInt("msgRes"))
.setTitle(requireArguments().getCharSequence("title"))
.setMessage(requireArguments().getCharSequence("msg"))
.setPositiveButton(android.R.string.ok) { _, _ -> /* Let it dismiss */ }
.create()
}

View file

@ -66,7 +66,7 @@ open class BaseViewModel(val app: Application) : AndroidViewModel(app) {
/**
* Executes given [block] asynchronously on IO thread.
*/
protected fun asyncIO(block: () -> Unit) = asyncIO(block, {})
protected fun asyncIO(block: () -> Unit) = asyncIO(block) {}
/**
* Executes given [block] asynchronously on IO thread.

View file

@ -9,12 +9,11 @@
package com.gaurav.avnc.viewmodel
import android.app.Application
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.lifecycle.viewModelScope
import com.gaurav.avnc.model.ServerProfile
import com.gaurav.avnc.vnc.Discovery
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class HomeViewModel(app: Application) : BaseViewModel(app) {
@ -57,27 +56,37 @@ class HomeViewModel(app: Application) : BaseViewModel(app) {
*/
val profileDeletedEvent = LiveEvent<ServerProfile>()
init {
if (pref.server.discoveryAutoStart)
startDiscovery()
}
/**
* Starts new connection to given profile.
*/
fun startConnection(profile: ServerProfile) = newConnectionEvent.fire(profile)
/**
* Starts discovery service.
* It will be automatically stopped after timeout.
*/
fun startDiscovery() {
discovery.start()
/**************************************************************************
* Server Discovery
*
* To save battery, Discovery is stopped when HomeActivity is in background.
**************************************************************************/
private var autoStopped = false
viewModelScope.launch {
runCatching { delay(pref.server.discoveryTimeout) }
discovery.stop()
fun startDiscovery() {
autoStopped = false
discovery.start()
}
fun stopDiscovery() {
autoStopped = false
discovery.stop()
}
fun autoStartDiscovery() {
if (pref.server.discoveryAutorun || autoStopped)
startDiscovery()
}
fun autoStopDiscovery() {
if (discovery.isRunning.value == true) {
stopDiscovery()
autoStopped = true
}
}
@ -116,4 +125,29 @@ class HomeViewModel(app: Application) : BaseViewModel(app) {
fun deleteProfile(profile: ServerProfile) = asyncIO({ serverProfileDao.delete(profile) }, {
profileDeletedEvent.fire(profile)
})
/**************************************************************************
* Rediscovery Indicator
*
* [rediscoveredProfiles] is the intersection of saved & discovered servers.
*
* To detect reachable server in [serverProfiles], we could directly 'ping'
* them, but that has its own issues.
**************************************************************************/
val rediscoveredProfiles by lazy {
Transformations.switchMap(pref.server.rediscoveryIndicator) {
if (it) prepareRediscoveredProfiles()
else MutableLiveData(null)
}!!
}
private fun prepareRediscoveredProfiles() = with(MediatorLiveData<List<ServerProfile>>()) {
val filter = { saved: List<ServerProfile>?, discovered: List<ServerProfile>? ->
saved?.filter { s -> discovered?.find { s.host == it.host && s.port == it.port } != null }
}
addSource(serverProfiles) { value = filter(it, discovery.servers.value) }
addSource(discovery.servers) { value = filter(serverProfiles.value, it) }
this
}
}

View file

@ -65,23 +65,47 @@ import java.lang.ref.WeakReference
*/
class VncViewModel(app: Application) : BaseViewModel(app), VncClient.Observer {
/**
* Connection lifecycle:
*
* Created
* |
* v
* Connecting ----------+
* | |
* v |
* Connected |
* | |
* v |
* Disconnected <-------+
*
*/
enum class State {
Created,
Connecting,
Connected,
Disconnected,
}
val client = VncClient(this)
/**
* Client state is exposed as live data here to allow dynamic data binding
* from layouts.
*/
val clientState = MutableLiveData(client.state)
/**
* Reason for [clientState] being [VncClient.State.Disconnected].
*/
val disconnectReason = MutableLiveData("")
/**
* [ServerProfile] used for current connection.
*/
var profile = ServerProfile()
var profile = ServerProfile(); private set
/**
* We have two places for connection state (both are synced):
*
* [VncClient.connected] - Simple boolean state, used most of the time
* [state] - More granular, used by observers & data binding
*/
val state = MutableLiveData(State.Created)
/**
* Reason for disconnecting.
*/
val disconnectReason = MutableLiveData("")
/**
* Fired when [VncClient] has asked for credential. It is used to
@ -124,11 +148,6 @@ class VncViewModel(app: Application) : BaseViewModel(app), VncClient.Observer {
*/
val frameScroller = FrameScroller(this)
/**
* Fired when size of the native framebuffer changes.
*/
val fbSizeChangedEvent = LiveEvent<FrameState>()
/**
* Used for sending events to remote server.
*/
@ -146,23 +165,16 @@ class VncViewModel(app: Application) : BaseViewModel(app), VncClient.Observer {
* Connection management
**************************************************************************/
/**
* Because [initConnection] can be called multiple times due to activity restarts,
* initialization inside it is performed only once.
*/
private var initialized = false
/**
* Initialize VNC connection using given profile.
* It may be called multiple times due to activity restarts.
*/
fun initConnection(profile: ServerProfile) {
if (initialized)
return
initialized = true
this.profile = profile
launchConnection()
if (state.value == State.Created) {
state.value = State.Connecting
this.profile = profile
launchConnection()
}
}
private fun launchConnection() {
@ -175,12 +187,12 @@ class VncViewModel(app: Application) : BaseViewModel(app), VncClient.Observer {
processMessages()
}.onFailure {
client.disconnect()
if (it is IOException)
disconnectReason.postValue(it.message)
if (it is IOException) disconnectReason.postValue(it.message)
Log.e("ReceiverCoroutine", "Connection failed", it)
}
state.postValue(State.Disconnected)
//Wait until activity is finished and viewmodel is cleaned up.
runCatching { awaitCancellation() }
cleanup()
@ -188,7 +200,7 @@ class VncViewModel(app: Application) : BaseViewModel(app), VncClient.Observer {
}
private fun configureClient() {
client.configure(profile.viewOnly, profile.securityType, profile.useLocalCursor)
client.configure(profile.viewOnly, profile.securityType, true /* Hardcoded to true */)
if (profile.useRepeater)
client.setupRepeater(profile.idOnRepeater)
@ -208,16 +220,13 @@ class VncViewModel(app: Application) : BaseViewModel(app), VncClient.Observer {
else -> throw IOException("Unknown Channel: ${profile.channelType}")
}
if (client.state != VncClient.State.Connected)
throw IOException(client.getLastErrorStr())
state.postValue(State.Connected)
sendClipboardText() //Initial sync
}
private fun processMessages() {
while (viewModelScope.isActive && client.processServerMessage()) {
//Message Loop
}
disconnectReason.postValue(client.getLastErrorStr())
while (viewModelScope.isActive)
client.processServerMessage()
}
private fun cleanup() {
@ -320,17 +329,13 @@ class VncViewModel(app: Application) : BaseViewModel(app), VncClient.Observer {
receiveClipboardText(text)
}
override fun onClientStateChanged(newState: VncClient.State) {
clientState.postValue(newState)
if (newState == VncClient.State.Connected)
sendClipboardText() //Initial sync
}
override fun onFramebufferSizeChanged(width: Int, height: Int) {
viewModelScope.launch(Dispatchers.Main) {
frameState.setFramebufferSize(width.toFloat(), height.toFloat())
fbSizeChangedEvent.fire(frameState)
}
}
override fun onPointerMoved(x: Int, y: Int) {
frameViewRef.get()?.requestRender()
}
}

View file

@ -76,14 +76,15 @@ class Discovery(private val context: Context) {
}
isRunning.value = true
servers.value = ArrayList() //Forget known servers
if (servers.value?.size != 0) servers.value = ArrayList() //Forget known servers
// Construction of NSD manager is done on a background thread because it appears to be quite heavy.
GlobalScope.launch(Dispatchers.Default) {
if (nsdManager == null)
nsdManager = context.getSystemService(Context.NSD_SERVICE) as NsdManager
nsdManager?.discoverServices(service, NsdManager.PROTOCOL_DNS_SD, listener)
runCatching { nsdManager?.discoverServices(service, NsdManager.PROTOCOL_DNS_SD, listener) }
.onFailure { Log.e(javaClass.simpleName, "Unable to start Discovery", it) }
}
}
@ -93,7 +94,8 @@ class Discovery(private val context: Context) {
*/
fun stop() {
if (isRunning.value == true) {
nsdManager?.stopServiceDiscovery(listener)
runCatching { nsdManager?.stopServiceDiscovery(listener) }
.onFailure { Log.e(javaClass.simpleName, "Unable to stop Discovery", it) }
}
}
@ -101,11 +103,11 @@ class Discovery(private val context: Context) {
* Adds a new profile with given details to list.
*/
private fun addProfile(name: String, host: String, port: Int) {
val profile = ServerProfile().apply {
this.name = name
this.host = host
this.port = port
}
val profile = ServerProfile(
name = name,
host = host,
port = port
)
runBlocking(Dispatchers.Main) {
val currentList = servers.value!!
@ -168,7 +170,6 @@ class Discovery(private val context: Context) {
*/
private inner class ResolveListener : NsdManager.ResolveListener {
override fun onServiceResolved(serviceInfo: NsdServiceInfo) {
Log.d(javaClass.simpleName, "Resolved service: ${serviceInfo.serviceName.take(3)}*******")
addProfile(serviceInfo.serviceName, serviceInfo.host.hostAddress, serviceInfo.port)
}

View file

@ -57,6 +57,7 @@ class Messenger(private val client: VncClient) {
private fun sendPointerEvent(mask: Int, p: PointF) {
val x = p.x.toInt()
val y = p.y.toInt()
client.moveClientPointer(x, y)
execute { client.sendPointerEvent(x, y, mask) }
}
@ -78,7 +79,7 @@ class Messenger(private val client: VncClient) {
}
fun sendKey(keySym: Int, isDown: Boolean): Boolean {
if (client.state != VncClient.State.Connected)
if (!client.connected)
return false
execute { client.sendKeyEvent(keySym, isDown) }

View file

@ -1,7 +1,7 @@
package com.gaurav.avnc.vnc
import androidx.annotation.Keep
import com.gaurav.avnc.vnc.VncClient.Observer
import java.io.IOException
import java.nio.ByteBuffer
import java.nio.charset.StandardCharsets
@ -29,39 +29,13 @@ import java.nio.charset.StandardCharsets
*
* To release the resources you must call [cleanup] after you are done with
* this instance.
*
* Note: All callbacks in [Observer] are invoked on the thread which called
* [processServerMessage].
*/
class VncClient(private val observer: Observer) {
/**
* [VncClient] Lifecycle:
*
* Created ---------------+
* | |
* v |
* Connecting ----------+ |
* | | |
* v | |
* Connected | |
* | | |
* v | |
* Disconnected <-------+ |
* | |
* v |
* Destroyed <-------------+
*/
enum class State {
Created,
Connecting,
Connected,
Disconnected,
Destroyed
}
/**
* Interface for event observer.
* DO NOT throw exceptions from these methods.
* There is NO guarantee about which thread will invoke [Observer] methods.
*/
interface Observer {
fun onPasswordRequired(): String
@ -69,10 +43,9 @@ class VncClient(private val observer: Observer) {
fun onGotXCutText(text: String)
fun onFramebufferUpdated()
fun onFramebufferSizeChanged(width: Int, height: Int)
fun onClientStateChanged(newState: State)
fun onPointerMoved(x: Int, y: Int)
//fun onBell()
//fun onCursorMoved(x: Int, y: Int): Boolean
}
/**
@ -86,14 +59,9 @@ class VncClient(private val observer: Observer) {
throw RuntimeException("Could not create native rfbClient!")
}
/**
* Current client state.
*/
var state: State = State.Created
private set(value) {
field = value
observer.onClientStateChanged(value)
}
@Volatile
var connected = false
private set
/**
* Name of remote desktop
@ -110,16 +78,33 @@ class VncClient(private val observer: Observer) {
*/
var viewOnlyMode = false; private set
/**
* Latest pointer position. See [moveClientPointer].
*/
var pointerX = 0; private set
var pointerY = 0; private set
/**
* Client-side cursor rendering creates a synchronization issue.
* Suppose if pointer is moved to (50,10) by client. A PointerEvent is sent
* to the server and cursor is immediately rendered on (50,10).
* Some servers (e.g. Vino) will send back a PointerPosition event for (50, 10).
* But, by the time that event is received from server, pointer on client
* might have already moved to (60,20) (this is almost guaranteed to happen
* with touchpad/relative action mode). So the cursor will probably 'jump back'
* depending on the order of these events.
*
* This flags works around the issue by temporarily ignoring serer-side updates.
*/
@Volatile
var ignorePointerMovesByServer = false
/**
* Setup different properties for this client.
* Only valid in [State.Connected] state.
*
* @param securityType RFB security type to use.
*/
fun configure(viewOnly: Boolean, securityType: Int, useLocalCursor: Boolean) {
if (state != State.Created)
return
viewOnlyMode = viewOnly
nativeConfigure(nativePtr, securityType, useLocalCursor)
}
@ -130,42 +115,25 @@ class VncClient(private val observer: Observer) {
/**
* Initializes VNC connection.
*
* @return true if initialization was successful
*/
fun connect(host: String, port: Int): Boolean {
if (state != State.Created)
return false
state = State.Connecting
if (nativeInit(nativePtr, host, port)) {
state = State.Connected
return true
} else {
state = State.Disconnected
return false
}
fun connect(host: String, port: Int) {
connected = nativeInit(nativePtr, host, port)
if (!connected) throw IOException(nativeGetLastErrorStr())
}
/**
* Waits for incoming server message, parses it and then invokes appropriate
* callbacks.
* Waits for incoming server message, parses it and then invokes appropriate callbacks.
*
* @param uSecTimeout Timeout in microseconds.
* @return true if message was successfully handled or no message was received within timeout,
* false otherwise.
*/
fun processServerMessage(uSecTimeout: Int = 1000000): Boolean {
if (state != State.Connected)
return false
fun processServerMessage(uSecTimeout: Int = 1000000) {
if (!connected)
return
if (!nativeProcessServerMessage(nativePtr, uSecTimeout)) {
disconnect() //Reason: Msg processing will only fail on irrecoverable errors
return false
connected = false
throw IOException(nativeGetLastErrorStr())
}
return true
}
/**
@ -189,6 +157,26 @@ class VncClient(private val observer: Observer) {
nativeSendPointerEvent(nativePtr, x, y, mask)
}
/**
* Updates client-side pointer position.
* No event is sent to server.
*
* Primary use-case is to update pointer position during gestures.
* This way we can immediately render the cursor on new position without
* waiting for Network IO.
*
* It also helps with servers which don't send pointer-position updates
* if pointer was moved by the client.
*
* @param x Horizontal pointer coordinate
* @param y Vertical pointer coordinate
*/
fun moveClientPointer(x: Int, y: Int) {
pointerX = x
pointerY = y
observer.onPointerMoved(x, y)
}
/**
* Sends text to remote desktop's clipboard.
@ -209,30 +197,16 @@ class VncClient(private val observer: Observer) {
fun uploadFrameTexture() = nativeUploadFrameTexture(nativePtr)
/**
* Marks this client as 'disconnected'.
*
* This doesn't actually release the resources from server but
* simply updates the state. [cleanup] is used for that.
* Upload cursor shape into framebuffer texture.
*/
fun disconnect() {
state = State.Disconnected
}
fun uploadCursor() = nativeUploadCursor(nativePtr, pointerX, pointerY)
/**
* Returns a string describing last error.
* Only valid in disconnected state.
*/
fun getLastErrorStr() = nativeGetLastErrorStr()
/**
* Releases all resource (native & managed) currently held.
* After cleanup, this client MUST NOT be used anymore.
* Release all resources allocated by the client.
* DO NOT use this client after [cleanup].
*/
fun cleanup() {
if (state == State.Destroyed)
return
state = State.Destroyed
connected = false
nativeCleanup(nativePtr)
}
@ -241,7 +215,7 @@ class VncClient(private val observer: Observer) {
* events to remote server.
*/
private inline fun executeSend(block: () -> Unit) {
if (state == State.Connected && !viewOnlyMode)
if (connected && !viewOnlyMode)
block()
}
@ -259,6 +233,7 @@ class VncClient(private val observer: Observer) {
private external fun nativeGetHeight(clientPtr: Long): Int
private external fun nativeIsEncrypted(clientPtr: Long): Boolean
private external fun nativeUploadFrameTexture(clientPtr: Long)
private external fun nativeUploadCursor(clientPtr: Long, px: Int, py: Int)
private external fun nativeGetLastErrorStr(): String
private external fun nativeCleanup(clientPtr: Long)
@ -284,8 +259,10 @@ class VncClient(private val observer: Observer) {
private fun cbBell() = Unit // observer.onBell()
@Keep
@Suppress("UNUSED_PARAMETER")
private fun cbHandleCursorPos(x: Int, y: Int) = true //observer.onCursorMoved(x, y)
private fun cbHandleCursorPos(x: Int, y: Int) {
if (!ignorePointerMovesByServer)
moveClientPointer(x, y)
}
/**

View file

@ -12,7 +12,7 @@
<data>
<import type="com.gaurav.avnc.vnc.VncClient.State" />
<import type="com.gaurav.avnc.viewmodel.VncViewModel.State" />
<variable
name="viewModel"
@ -41,7 +41,7 @@
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:isVisible="@{viewModel.clientState == State.Connected}">
app:isVisible="@{viewModel.state == State.Connected}">
<!--FrameView-->
@ -99,7 +99,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?colorSurface"
app:isVisible="@{viewModel.clientState != State.Connected}">
app:isVisible="@{viewModel.state != State.Connected}">
<ProgressBar
style="@style/Widget.AppCompat.ProgressBar"
@ -107,7 +107,7 @@
android:layout_height="60dp"
android:indeterminate="true"
android:padding="@dimen/padding_small"
app:isVisible="@{viewModel.clientState != State.Disconnected}"
app:isVisible="@{viewModel.state == State.Connecting}"
app:layout_constraintBottom_toTopOf="@id/client_state"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
@ -117,7 +117,7 @@
android:layout_height="60dp"
android:importantForAccessibility="no"
android:src="@drawable/ic_warning"
app:isVisible="@{viewModel.clientState == State.Disconnected}"
app:isVisible="@{viewModel.state == State.Disconnected}"
app:layout_constraintBottom_toTopOf="@id/client_state"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@ -128,8 +128,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_normal"
android:text="@{viewModel.clientState.toString()}"
app:isVisible="@{viewModel.clientState != State.Created}"
android:text="@{viewModel.state.toString()}"
app:isVisible="@{viewModel.state != State.Created}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@ -144,7 +144,7 @@
android:gravity="center"
android:text='@{ "( " + viewModel.disconnectReason + " )"}'
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
app:isVisible="@{viewModel.clientState == State.Disconnected &amp;&amp; !viewModel.disconnectReason.empty}"
app:isVisible="@{viewModel.state == State.Disconnected &amp;&amp; !viewModel.disconnectReason.empty}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/client_state"
@ -156,7 +156,7 @@
android:background="@drawable/bg_circular_button"
android:contentDescription="@string/desc_retry_connection_btn"
android:src="@drawable/ic_refresh"
app:isVisible="@{viewModel.clientState == State.Disconnected}"
app:isVisible="@{viewModel.state == State.Disconnected}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"

View file

@ -7,6 +7,7 @@
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
@ -32,10 +33,10 @@
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_large"
android:layout_marginBottom="@dimen/margin_normal"
android:drawableEnd="@drawable/ic_gpl"
android:drawablePadding="@dimen/padding_small"
android:text='@{@string/app_name + " is released under the GNU General Public License, version 3 or later."}'
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" />
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
app:drawableEndCompat="@drawable/ic_gpl" />
<TextView

View file

@ -144,18 +144,6 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/use_repeater" />
<!--Local cursor mode (Currently it is only used for 'hiding' remote cursor)-->
<CheckBox
android:id="@+id/use_local_cursor"
style="@style/FormField.CheckBox"
android:checked="@={profile.useLocalCursor}"
android:text="@string/title_hide_remote_cursor"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/view_only" />
<!--Key Compatibility mode-->
<CheckBox
android:id="@+id/key_compat_mode"
@ -164,7 +152,7 @@
android:text="@string/title_key_compat_mode"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/use_local_cursor" />
app:layout_constraintTop_toBottomOf="@id/view_only" />
<ImageButton
android:id="@+id/key_compat_mode_help_btn"

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (c) 2022 Gaurav Ujjwal.
~
~ SPDX-License-Identifier: GPL-3.0-or-later
~
~ See COPYING.txt for more details.
-->
<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/help_btn"
style="@style/ImageButton"
android:contentDescription="@string/desc_help_btn"
android:src="@drawable/ic_help"
app:tint="?colorControlNormal" />

View file

@ -34,7 +34,7 @@
style="@style/TipView"
android:text="@string/tip_discovery"
android:visibility="gone"
app:isVisible="@{!viewModel.discovery.isRunning &amp;&amp; viewModel.discovery.servers.empty}"
app:isVisible="@{viewModel.discovery.servers.empty}"
tools:visibility="visible" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
@ -57,5 +57,15 @@
android:contentDescription="@string/desc_discovery_running"
android:indeterminate="true"
app:isVisible="@{viewModel.discovery.isRunning}" />
<ImageButton
style="@style/ImageButton"
android:layout_gravity="end|bottom"
android:layout_margin="@dimen/margin_normal"
android:contentDescription="@string/desc_discovery_stop_btn"
android:onClick="@{()-> viewModel.stopDiscovery()}"
android:src="@drawable/ic_clear"
app:isVisible="@{viewModel.discovery.isRunning}"
app:tint="?colorSecondary" />
</FrameLayout>
</layout>

View file

@ -26,8 +26,7 @@
layout="@layout/server_item"
app:profile="@{viewModel}" />
<!--Experimental Feature-->
<View
<com.gaurav.avnc.ui.home.IndicatorView
android:id="@+id/indicator"
android:layout_width="10dp"
android:layout_height="10dp"

View file

@ -45,7 +45,7 @@
android:contentDescription="Hide Virtual Keys"
android:minWidth="30dp"
android:src="@drawable/ic_clear"
android:tint="?colorError" />
app:tint="?colorError" />
<!--~~~~~~~~~~~~~~~~~~~~~~~~~~ Meta keys ~~~~~~~~~~~~~~~~~~~~~-->

View file

@ -0,0 +1,179 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="title_add_server_profile">Server hinzufügen</string>
<string name="title_copy_name">Bezeichnung kopieren</string>
<string name="title_duplicate">Duplizieren</string>
<string name="title_delete">Entfernen</string>
<string name="title_copy_host">Hostname kopieren</string>
<string name="title_advanced">Erweitert</string>
<string name="title_undo">Rückgängig machen</string>
<string name="title_settings">Einstellungen</string>
<string name="title_credentials_required">Anmelden</string>
<string name="title_remember">Speichern</string>
<string name="title_export">Exportieren</string>
<string name="title_import">Importieren</string>
<string name="title_view_only_mode">Nur anzeigen</string>
<string name="title_key_compat_mode">Legacy key events senden</string>
<string name="title_use_ssh_tunnel">Durch SSH tunneln</string>
<string name="title_password">Passwort</string>
<string name="title_use_repeater">Repeater nutzen</string>
<string name="title_private_key">Privater Schlüssel</string>
<string name="title_change">Ändern</string>
<string name="title_about">Über</string>
<string name="title_report_bug">Melde einen Fehler</string>
<string name="title_github_repo">Quelltext</string>
<string name="hint_urlbar">Verbinde dich mit einem Server</string>
<string name="hint_server_name">Bezeichnung (z.B. Arbeits-PC)</string>
<string name="hint_repeater_host">Hostname/IP-Adresse des Repeaters</string>
<string name="msg_about_app">Ein schneller und sicherer VNC-Client. Beobachten und steuern Sie jedes entfernte Gerät, auf welchem ein VNC-Server läuft.
\n
\n
\nAVNC ist eine Open-Source- und Copyleft-Software, die mit Hilfe vieler Mitwirkender erstellt wurde.</string>
<string name="msg_export_auth_missing">Dein Gerät ist nicht gesichert. Aktiviere eine Displaysperre, um die das Exportieren damit zu authentifizieren.</string>
<string name="msg_invalid_key_file">Ungültiger private key</string>
<string name="msg_invalid_vnc_uri">Ungültige VNC-URI</string>
<string name="msg_copied_to_clipboard">Kopiert</string>
<string name="msg_key_compat_mode_help">Einige Server unterstützen Unicode-Zeichen nur teilweise. Legacy key events kann die Eingabe von nicht-englischem Text ermöglichen.</string>
<string name="tip_empty_server_list">Die Serverliste ist leer.
\nDrücke \'<b>+</b>\', um einen Server hinzuzufügen, oder
\nnutze die Adresszeile oben, um direkt eine Verbindung aufzubauen.</string>
<string name="tip_urlbar">Du kannst eine IP-Adresse, einen Hostnamen oder eine \'vnc://\'-URI eingeben.</string>
<string name="tip_discovery">Discovery kann unterstützte Server
\nin deinem Netzwerk automatisch finden.</string>
<string name="desc_navigation_btn">Navigationsbereich öffnen</string>
<string name="desc_discovered_servers_tab">Entdeckte Server</string>
<string name="desc_add_new_server_btn">Server hinzufügen</string>
<string name="desc_discovery_btn">Server entdecken</string>
<string name="desc_discovery_stop_btn">Suche nach Servern beenden</string>
<string name="desc_discovery_running">Suche Server in deinem Netzwerk</string>
<string name="desc_keyboard_btn">Öffne die Bildschirmtastatur</string>
<string name="desc_virtual_keys_btn">Zeige virtuelle Tasten</string>
<string name="desc_retry_connection_btn">Erneut versuchen</string>
<string name="desc_clear_url_btn">Löschen</string>
<string name="desc_help_btn">Hilfe</string>
<string name="desc_saved_servers_list">Liste der abgespeicherten Server</string>
<string name="pref_ui_summary">Optik und Handhabung</string>
<string name="pref_viewer">Fernsteuerung</string>
<string name="pref_viewer_summary">Bild in Bild, Zoomstufen</string>
<string name="pref_input_summary">Gesten, Mauszeiger, virtuelle Tasten</string>
<string name="pref_servers">Server</string>
<string name="pref_tools">Werkzeuge</string>
<string name="pref_tools_summary">Importieren/Exportieren, Debugging</string>
<string name="pref_experimental">Experimentelles</string>
<string name="pref_experimental_summary">Weitere Optionen</string>
<string name="pref_gesture_action_none">Nichts</string>
<string name="pref_gesture_action_pan">Lokale Ansicht verschieben</string>
<string name="pref_theme">Design</string>
<string name="pref_theme_option_system">System</string>
<string name="pref_sort_server_list">Sortiere die gespeicherten Server nach ihren Namen</string>
<string name="pref_gesture">Gesten</string>
<string name="pref_swipe2">Wischen mit zwei Fingern</string>
<string name="pref_drag">Ziehen (Drag and Drop)</string>
<string name="pref_mouse_passthrough">Maus-Passthrough</string>
<string name="pref_mouse_passthrough_summary_off">Maus-Bewegungen für lokale Gesten verwenden</string>
<string name="pref_hide_local_cursor">Lokalen Mauszeiger verstecken</string>
<string name="pref_mouse_back_action_default">Standard</string>
<string name="pref_vk_show_all">Zeige alle Tasten</string>
<string name="pref_vk_open_with_keyboard">Mit der Tastatur öffnen</string>
<string name="pref_km">Tastenzuordnungen</string>
<string name="pref_km_back_to_escape">Zurück → Escape</string>
<string name="pref_km_language_switch_to_super">Sprachwechsel → Super</string>
<string name="pref_km_right_alt_to_super">Rechte Alt → Super</string>
<string name="pref_km_back_to_escape_summary">Betrifft nicht die Navigationsleiste</string>
<string name="pref_fullscreen">Vollbild</string>
<string name="pref_toolbar">Werkzeugleiste</string>
<string name="pref_toolbar_alignment">Ausrichtung</string>
<string name="pref_toolbar_alignment_option_end">Rechts</string>
<string name="pref_zoom">Zoom</string>
<string name="pref_zoom_min">Minimum</string>
<string name="pref_credential">Anmelden</string>
<string name="pref_discovery_autorun">Automatische Suche</string>
<string name="pref_discovery">Automatische Suche</string>
<string name="pref_import_export">Server ex- und importieren</string>
<string name="pref_logs">Protokolle / Logs</string>
<string name="pref_experimental_toolbar_swipe_close_summary">Während einer Fernwartung können Sie die aktuell geöffnete Symbolleiste durch Wischen schließen</string>
<string name="pref_experimental_immersive_mode">Immersiver Modus</string>
<string name="title_edit_server_profile">Server bearbeiten</string>
<string name="title_edit">Bearbeiten</string>
<string name="title_clear">Löschen</string>
<string name="title_cancel">Abbrechen</string>
<string name="title_save">Speichern</string>
<string name="title_hide_remote_cursor">Entfernten Mauszeiger ausblenden</string>
<string name="msg_server_profile_added">Server hinzugefügt</string>
<string name="msg_server_profile_deleted">Server entfernt</string>
<string name="msg_ptr_hiding_not_supported">Wird auf diesem Gerät nicht unterstützt</string>
<string name="hint_username">Benutzername</string>
<string name="hint_password">Passwort</string>
<string name="title_delete_servers_before_import">Zurzeit gespeicherte Server vor dem Importieren löschen</string>
<string name="title_continue">Fortfahren</string>
<string name="hint_host">Hostname/IP-Adresse</string>
<string name="hint_port">Port</string>
<string name="title_license">Lizenz</string>
<string name="title_open_source_libraries">Open-Source-Bibliotheken</string>
<string name="hint_key_password">Passwort des Schlüssels</string>
<string name="msg_pip_not_supported">Wird auf diesem Gerät nicht unterstützt</string>
<string name="msg_loading">Lädt…</string>
<string name="msg_imported">Importiert</string>
<string name="msg_export_auth_required">Authentifiziere dich, um die Server zu exportieren</string>
<string name="msg_exported">Exportiert</string>
<string name="pref_long_press">Tippen und gedrückt halten</string>
<string name="pref_mouse_back">Zurück-Schaltfläche</string>
<string name="desc_back_btn">Zurück</string>
<string name="desc_settings_btn">Einstellungen</string>
<string name="desc_saved_servers_tab">Gespeicherte Server</string>
<string name="desc_reset_zoom_btn">Setze die Zoom-Stufe zurück</string>
<string name="pref_ui">Benutzeroberfläche</string>
<string name="desc_save_discovered_server_btn">Speichern</string>
<string name="desc_server_url">Server-Adresse</string>
<string name="desc_discovered_list">Liste der automatisch gefundenen Server</string>
<string name="pref_input">Eingabe</string>
<string name="pref_server_summary">Login, automatische Suche</string>
<string name="pref_gesture_action_move_pointer">Mauszeiger bewegen</string>
<string name="pref_gesture_action_left_click">Linksklick</string>
<string name="pref_gesture_action_middle_click">Mittelklick</string>
<string name="pref_gesture_action_right_click">Rechtsklick</string>
<string name="pref_gesture_action_remote_scroll">entfernte Ansicht scrollen</string>
<string name="pref_gesture_action_open_keyboard">Tastatur einblenden</string>
<string name="pref_theme_option_dark">Dunkel</string>
<string name="pref_theme_option_light">Hell</string>
<string name="pref_gesture_action_double_click">Doppelklick</string>
<string name="pref_prefer_advanced_editor">Zeige sofort die erweiterten Server-Optionen an</string>
<string name="pref_two_finger_tap">Tippen mit zwei Fingern</string>
<string name="pref_swipe_sensitivity">Wisch-Empfindlichkeit</string>
<string name="pref_mouse_passthrough_summary_on">Maus-Bewegungen direkt an den Server durchreichen</string>
<string name="pref_single_tap">Einfaches Tippen</string>
<string name="pref_swipe1">Wischen mit einem Finger</string>
<string name="pref_mouse">Maus</string>
<string name="pref_double_tap">Doppeltippen</string>
<string name="pref_mouse_back_action_right">Rechtsklick</string>
<string name="pref_vk">Virtuelle Tasten</string>
<string name="pref_mouse_back_action_middle">Mittelklick</string>
<string name="pref_toolbar_alignment_option_start">Links</string>
<string name="pref_enable_pip">Bild in Bild</string>
<string name="pref_zoom_max">Maximum</string>
<string name="pref_clipboard_sync">Zwischenablage syncronisieren</string>
<string name="pref_cred_autocomplete">Auto-Vervollständigung</string>
<string name="pref_cred_autocomplete_summary">Schlage gespeicherte Zugangsdaten bei neuen Servern vor</string>
<string name="pref_keycode_map">Keycode-Übersicht</string>
<string name="pref_experimental_toolbar_swipe_close">Wischen zum Schließen der Symbolleiste</string>
<string name="pref_discovery_autorun_summary">Schon auf der Startseite automatisch nach Servern suchen</string>
<string name="pref_rediscovery_indicator">Wiederentdeckte Server hervorheben</string>
<string name="pref_debugging">Fehlersuche</string>
<string name="pref_experimental_immersive_mode_summary">Blendet im Vollbildmodus Benachrichtigungs- und Navigationsleiste aus</string>
<string name="title_unknown_ssh_host">Unbekannter SSH-Zielpunkt</string>
<string name="title_default_security">Standard-Sicherheit</string>
<string name="title_ssh_host_key_changed">Der öffentliche Schlüssel des SSH-Zielpunktes hat sich geändert</string>
<string name="hint_server_id">Server-ID</string>
<string name="pref_gesture_action_remote_drag">Drag and Drop für entfernte Inhalte</string>
<string name="pref_direct_touch">Direkte Touch-Umsetzung auf dem Server</string>
<string name="pref_direct_touch_summary_on">Setze Aktionen direkt am Ort des Tippens um</string>
<string name="pref_direct_touch_summary_off">Setze Aktionen an einem Mauszeiger um, ähnlich einem Touchpad</string>
<string name="pref_orientation">Bildschirmausrichtung</string>
<string name="pref_orientation_option_auto">Automatisch (nach Lagesensor)</string>
<string name="pref_orientation_option_portrait">Hochformat</string>
<string name="pref_orientation_option_landscape">Querformat</string>
<string name="msg_drag_gesture_help">Der Ziehgeste eine Aktion zuzuweisen, wird die Erkennung von langem Drücken ändern:
\n
\n<b>Drücken-Halten-Loslassen</b> → Lange drücken
\n<b>Drücken-Halten-Wischen</b> → Drag and Drop</string>
</resources>

View file

@ -18,16 +18,16 @@
<string name="title_save">ذخیره</string>
<string name="title_undo">برگردان</string>
<string name="title_settings">تنظیمات</string>
<string name="title_credentials_required">مدارک مورد نیاز</string>
<string name="title_remember">یادآوری</string>
<string name="title_credentials_required">ورود</string>
<string name="title_remember">به یاد داشتن</string>
<string name="title_export">برون‌ریزی</string>
<string name="title_import">درون‌ریزی</string>
<string name="title_view_only_mode">حالت فقط مشاهده</string>
<string name="title_hide_remote_cursor">پنهان کردن مکان‌نمای دوردست</string>
<string name="title_key_compat_mode">حالت سازگاری صفحه کلید</string>
<string name="title_use_ssh_tunnel">استفاده از تونل SSH</string>
<string name="title_unknown_ssh_host">میزبان SSH ناشناخته</string>
<string name="title_ssh_host_key_changed">کلید میزبان تغییر کرد!</string>
<string name="title_view_only_mode">فقط مشاهده</string>
<string name="title_hide_remote_cursor">پنهان کردن اشاره‌گر دوردست</string>
<string name="title_key_compat_mode">فرستادن رویدادهای کلید قدیمی</string>
<string name="title_use_ssh_tunnel">استفاده از تونل پوستهٔ امن</string>
<string name="title_unknown_ssh_host">میزبان ناشناختهٔ پوستهٔ امن</string>
<string name="title_ssh_host_key_changed">کلید میزبان پوستهٔ امن تغییر کرد</string>
<string name="title_continue">ادامه</string>
<string name="title_delete_servers_before_import">حذف کارسازهای کنونی پیش از درون‌ریزی</string>
<string name="title_password">گذرواژه</string>
@ -36,9 +36,9 @@
<string name="title_change">تغییر</string>
<string name="title_about">درباره</string>
<string name="title_license">پروانه</string>
<string name="title_open_source_libraries">کتاب‌خانه‌های متن‌باز</string>
<string name="title_open_source_libraries">کتابخانه‌های متن‌باز</string>
<string name="title_report_bug">گزارش یک اشکال</string>
<string name="title_github_repo">مخزن گیت‌هاب</string>
<string name="title_github_repo">کد منبع</string>
<string name="hint_urlbar">اتّصال به یک کارساز</string>
<string name="hint_server_name">نام (مثلا: رایانهٔ کاری)</string>
<string name="hint_host">میزبان</string>
@ -48,42 +48,46 @@
<string name="hint_username">نام کاربری</string>
<string name="hint_password">گذرواژه</string>
<string name="hint_key_password">گذرواژهٔ کلید</string>
<string name="msg_about_app">برنامهٔ AVNC یک کارخواه متن‌باز VNC برای اندروید است. این به شما اجازهٔ کنترل از راه دور هر افزاره‌ای که یک کارساز VNC را اجرا کند، را می‌دهد.</string>
<string name="msg_server_profile_added">کارساز اضافه شد.</string>
<string name="msg_server_profile_deleted">کارساز حذف شد.</string>
<string name="msg_pip_not_supported">تصویر در تصویر، در این افزاره پشتیبانی نمی‌شود</string>
<string name="msg_ptr_hiding_not_supported">پنهان کردن مکان‌نما ، در این افزاره پشتیبانی نمی‌شود</string>
<string name="msg_about_app">یک کارخواه سریع و ایمن VNC. به صورت دوردست هر افزاره‌ای که یک کارساز VNC را اجرا کند را ببینید و کنترل کنید.
\n
\n
\nبرنامهٔ AVNC یک نرم‌افزار متن‌باز و کپی‌لفت است — که با کمک همیاری مشارکت‌کنندگان ساخته شده است.</string>
<string name="msg_server_profile_added">کارساز اضافه شد</string>
<string name="msg_server_profile_deleted">کارساز حذف شد</string>
<string name="msg_pip_not_supported">در این افزاره پشتیبانی نمی‌شود</string>
<string name="msg_ptr_hiding_not_supported">در این افزاره پشتیبانی نمی‌شود</string>
<string name="msg_loading">در حال بار کردن …</string>
<string name="msg_imported">باموفّقیت درون‌ریزی شد</string>
<string name="msg_exported">باموفّقیت برون‌ریزی شد</string>
<string name="msg_export_auth_required">پیش از برون‌ریزی احراز هویت لازم است.</string>
<string name="msg_export_auth_missing">افزارهٔ شما ایمن نیست. پیش از برون‌ریزی قفل صفحتان را برای بکار انداختن احراز هویت، روشن کنید.</string>
<string name="msg_invalid_key_file">پرونده یک کلید خصوصی معتبر نیست!</string>
<string name="msg_imported">درون‌ریزی شد</string>
<string name="msg_exported">برون‌ریزی شد</string>
<string name="msg_export_auth_required">احراز هویت برای برون‌ریزی کارسازها</string>
<string name="msg_export_auth_missing">افزارهٔ شما ایمن نیست. پیش از برون‌ریزی قفل صفحه‌تان را برای بکار انداختن احراز هویت، روشن کنید.</string>
<string name="msg_invalid_key_file">پروندهٔ کلید خصوصی نامعتبر</string>
<string name="msg_invalid_vnc_uri">VNC URI نامعتبر</string>
<string name="msg_copied_to_clipboard">رونویسی شد!</string>
<string name="msg_key_compat_mode_help">بعضی کارسازها از ورود مستقیم یونیکد پشتیبانی نمی‌کنند. حالت سازگاری تلاش می‌کند که رویدادهای کلید را در قالب قدیمی ارسال کند.\n\nاگر با وارد کردن بعضی نویسه‌ها مشکل دارید، سعی کنید این ویژگی را فعّال/غیرفعّال کنید.</string>
<string name="tip_empty_server_list">فهرست کارسازها خالی است.\nاز دکمهٔ «<b>+</b>» برای افزودن کارساز استفاده کنید،\nیا از نوار نشانی بالا برای اتّصال مستقیم به کارساز استفاده کینید.</string>
<string name="msg_copied_to_clipboard">رونویسی شد</string>
<string name="msg_key_compat_mode_help">برخی از کارسازها پشتیبانی یونیکد محدودی دارند. رویدادهای کلید قدیمی می‌تواند به وارد کردن متن غیر انگلیسی کمک کند.</string>
<string name="tip_empty_server_list">فهرست کارسازها خالی است.
\nبرای افزودن کارساز بر روی «<b>+</b>» کلیک کنید، یا
\nاز نوار نشانی بالا برای اتّصال مستقیم استفاده کنید.</string>
<!-- در رشته زیر از نویسهٔ LRM استفاده شده است -->
<string name="tip_urlbar">شما می‌توانید نام میزبان یا نشانی آی‌پی را وارد کنید.\nهمچنین نشانی‌ها به صورت «vnc://‎» نیز پشتیبانی می‌شوند.</string>
<string name="tip_urlbar">شما می‌توانید نشانی آی‌پی، نام میزبان یا نشانی «vnc://‎» را وارد کنید.</string>
<string name="tip_discovery">اکتشاف می‌تواند به صورت خودکار کارسازهای پشتیبانی‌شده\nکه در شبکهٔ شما در حال اجرا هستند، را بیابد.</string>
<!-- Accessibility descriptions -->
<string name="desc_navigation_btn">گشودن کشوی ناوش</string>
<string name="desc_settings_btn">گشودن تنظیمات</string>
<string name="desc_settings_btn">تنظیمات</string>
<string name="desc_saved_servers_tab">کارسازهای ذخیره شده</string>
<string name="desc_discovered_servers_tab">کارسازهای کشف شده</string>
<string name="desc_add_new_server_btn">افزودن کارساز جدید</string>
<string name="desc_save_discovered_server_btn">ذخیرهٔ این کارساز</string>
<string name="desc_discovery_btn">شروع اکتشاف کارساز</string>
<string name="desc_add_new_server_btn">افزودن کارساز</string>
<string name="desc_save_discovered_server_btn">ذخیره</string>
<string name="desc_discovery_btn">اکتشاف کارسازها</string>
<string name="desc_discovery_running">درحال اکتشاف کارسازها در شبکهٔ شما</string>
<string name="desc_keyboard_btn">گشودن صفحه‌کلید</string>
<string name="desc_virtual_keys_btn">نمایش کلیدهای مجازی</string>
<!-- در رشته زیر از نویسه‌های ایزوله چپ‌به‌راست استفاده شده است -->
<string name="desc_reset_zoom_btn">بازنشانی بزرگنمایی به ⁦100%⁩</string>
<string name="desc_retry_connection_btn">تلاش مجدد برای اتّصال</string>
<string name="desc_reset_zoom_btn">بازنشانی بزرگنمایی</string>
<string name="desc_retry_connection_btn">تلاش مجدد</string>
<string name="desc_back_btn">بازگشت</string>
<string name="desc_clear_url_btn">پاک‌کردن نشانی</string>
<string name="desc_server_url">نشانی کارساز VNC را وارد کنید</string>
<string name="desc_help_btn">نمایش راهنما</string>
<string name="desc_clear_url_btn">پاک‌کردن</string>
<string name="desc_server_url">نشانی کارساز</string>
<string name="desc_help_btn">راهنما</string>
<string name="desc_discovered_list">فهرست کارسازهای به صورت خودکار کشف شده</string>
<string name="desc_saved_servers_list">فهرست کارسازهای ذخیره شده</string>
<!-- Preference -->
@ -94,78 +98,82 @@
<string name="pref_input">ورودی</string>
<string name="pref_input_summary">حرکات، ماوس، کلیدهای مجازی</string>
<string name="pref_servers">کارسازها</string>
<string name="pref_server_summary">اکتشاف، اعتبار</string>
<string name="pref_server_summary">ورود، اکتشاف</string>
<string name="pref_tools">ابزار</string>
<string name="pref_tools_summary">درون‌ریزی/برون‌ریزی، اشکال زدایی</string>
<string name="pref_experimental">آزمایشی</string>
<string name="pref_experimental_summary">چیزهای اضافی!</string>
<string name="pref_experimental_summary">چیزهای اضافی</string>
<string name="pref_gesture_action_none">هیچ</string>
<string name="pref_gesture_action_left_click">کلیک چپ</string>
<string name="pref_gesture_action_middle_click">کلیک وسط</string>
<string name="pref_gesture_action_right_click">کلیک راست</string>
<string name="pref_gesture_action_double_click">دوبار کلیک</string>
<string name="pref_gesture_action_left_click">کلیک-چپ</string>
<string name="pref_gesture_action_middle_click">کلیک-وسط</string>
<string name="pref_gesture_action_right_click">کلیک-راست</string>
<string name="pref_gesture_action_double_click">دوبار-کلیک</string>
<string name="pref_gesture_action_move_pointer">جابه‌جایی اشاره‌گر</string>
<string name="pref_gesture_action_pan">حرکت دادن قاب محلّی</string>
<string name="pref_gesture_action_remote_scroll">لغزش محتوای دوردست</string>
<string name="pref_gesture_action_remote_drag">کشیدن محتوای دوردست</string>
<string name="pref_gesture_action_open_keyboard">گشودن صفحه‌کلید</string>
<string name="pref_theme">زمینه</string>
<string name="pref_prefer_advanced_editor">ترجیح دادن ویرایشگر پیشرفته</string>
<string name="pref_sort_server_list">مرتب‌سازی فهرست کارسازها</string>
<string name="pref_prefer_advanced_editor">گشودن مستقیم گزینه‌های پیشرفتهٔ کارساز</string>
<string name="pref_sort_server_list">مرتب‌سازی کارسازهای ذخیره شده بر اساس نام</string>
<string name="pref_gesture">حرکات</string>
<string name="pref_single_tap">یک ضربه</string>
<string name="pref_two_finger_tap">ضربهٔ دو انگشتی</string>
<string name="pref_double_tap">دو ضربه</string>
<string name="pref_two_finger_tap">ضربهٔ دو-انگشتی</string>
<string name="pref_double_tap">دوبار ضربه</string>
<string name="pref_long_press">فشار طولانی</string>
<string name="pref_swipe1">سُر دادن یک انگشت</string>
<string name="pref_swipe2">سُر دادن دو انگشت</string>
<string name="pref_swipe1">سُر دادن یک-انگشت</string>
<string name="pref_swipe2">سُر دادن دو-انگشت</string>
<string name="pref_drag">کشیدن (فشار طولانی و سُر دادن)</string>
<string name="pref_mouse">ماوس</string>
<string name="pref_mouse_passthrough">عبور ماوس</string>
<string name="pref_mouse_passthrough_summary_off">رویدادهای ماوس به صورت محلّی برای حرکات استفاده می‌شوند</string>
<string name="pref_mouse_passthrough_summary_on">رویدادهای ماوس به صورت مستقیم برای کارساز فرستاده می‌شوند</string>
<string name="pref_hide_local_cursor">پنهان کردن مکان‌نمای محلّی (اندروید)</string>
<string name="pref_mouse_passthrough_summary_off">رویدادهای ماوس برای حرکات محلّی استفاده شوند</string>
<string name="pref_mouse_passthrough_summary_on">رویدادهای ماوس به صورت مستقیم به کارساز فرستاده شوند</string>
<string name="pref_hide_local_cursor">پنهان کردن اشاره‌گر محلّی</string>
<string name="pref_vk">کلیدهای مجازی</string>
<string name="pref_vk_show_all">نمایش همهٔ کلیدها</string>
<string name="pref_vk_open_with_keyboard">نمایش هنگامی که صفحه‌کلید گشوده است</string>
<string name="pref_vk_open_with_keyboard">گشودن همراه صفحه‌کلید</string>
<string name="pref_km">نقشه‌کشی کلیدها</string>
<string name="pref_km_right_alt_to_super">دگرساز راست به سوپر</string>
<string name="pref_km_language_switch_to_super">تعویض زبان به سوپر</string>
<string name="pref_km_back_to_escape">بازگشت به گریز</string>
<string name="pref_km_back_to_escape_summary">نوار ناوش تحت تأثیر این قرار نمی‌گیرد</string>
<string name="pref_fullscreen">استفاده از تمام صفحه</string>
<string name="pref_enable_pip">بکار انداختن تصویر در تصویر</string>
<string name="pref_km_right_alt_to_super">دگرساز راست ← سوپر</string>
<string name="pref_km_language_switch_to_super">تعویض زبان ← سوپر</string>
<string name="pref_km_back_to_escape">بازگشت ← گریز</string>
<string name="pref_km_back_to_escape_summary">بر نوار ناوش تأثیر نمی‌گذارد</string>
<string name="pref_fullscreen">تمام صفحه</string>
<string name="pref_enable_pip">تصویر در تصویر</string>
<string name="pref_toolbar">نوار ابزار</string>
<string name="pref_toolbar_alignment">تراز</string>
<string name="pref_zoom">بزرگنمایی</string>
<string name="pref_zoom_min">بیشینهٔ بزرگنمایی</string>
<string name="pref_zoom_max">بیشینهٔ بزرگنمایی</string>
<string name="pref_zoom_min">کمینه</string>
<string name="pref_zoom_max">بیشینه</string>
<string name="pref_clipboard_sync">همگام‌سازی تخته‌گیره</string>
<string name="pref_credential">اعتبارنامه</string>
<string name="pref_cred_autocomplete">به کار انداختن تکمیل خودکار</string>
<string name="pref_cred_autocomplete_summary">هنگام اتّصال به کارسازی جدید، اعتبارنامه‌های شناخته شده را پیشنهاد می‌دهد</string>
<string name="pref_credential">ورود</string>
<string name="pref_cred_autocomplete">تکمیل خودکار</string>
<string name="pref_cred_autocomplete_summary">هنگام اتّصال به کارسازهای جدید، ورودهای ذخیره شده را پیشنهاد می‌دهد</string>
<string name="pref_discovery">اکتشاف</string>
<string name="pref_discovery_timeout">زمان انقضا (ثانیه)</string>
<string name="pref_discovery_auto_start">شروع خودکار</string>
<string name="pref_discovery_auto_start_summary">شروع اکتشاف هنگامی که کاره شروع شد</string>
<string name="pref_discovery_restart">شروع مجدد</string>
<string name="pref_discovery_restart_summary">شروع مجدد اکتشاف پس از توقّف آن</string>
<string name="pref_discovery_restart_delay">تأخیر شروع مجدد (دقسقه)</string>
<string name="pref_import_export">درون‌ریزی/برون‌ریزی کارسازها</string>
<string name="pref_debugging">اشکال زدایی</string>
<string name="pref_logs">گزارشات</string>
<string name="pref_keycode_map">نقشهٔ کد کلید</string>
<string name="pref_experimental_toolbar_swipe_close">برای بستن کشوی نوار ابزار، انگشت خود را بکشید</string>
<string name="pref_experimental_toolbar_swipe_close_summary">در طول یک جلسهٔ از راه دور، می‌توانید برای بستن نوار ابزار در حال حاضر باز، انگشت خود را بکشید</string>
<string name="pref_experimental_indicator">نشانگر اکتشاف برای کارسازهای ذخیره شده</string>
<string name="pref_experimental_indicator_summary">کارسازهای ذخیره شده را که توسط اکتشاف نیز یافت می‌شوند، برجسته می‌کند.
\nنیاز به راه‌اندازی مجدد برنامه دارد.</string>
<string name="pref_experimental_immersive_mode">حالت کاملاً فراگیر</string>
<string name="pref_experimental_immersive_mode_summary">تمام نوارهای سامانه را در حالت تمام صفحه پنهان می‌کند</string>
<string name="title_clear">پاک‌کردن</string>
<string name="pref_theme_option_light">روشن</string>
<string name="pref_toolbar_alignment_option_end">پایان</string>
<string name="pref_theme_option_system">پیروی از زمینهٔ سامانه</string>
<string name="pref_theme_option_system">سامانه</string>
<string name="pref_theme_option_dark">تاریک</string>
<string name="pref_toolbar_alignment_option_start">شروع</string>
<string name="pref_direct_touch">لمس مستقیم</string>
<string name="pref_direct_touch_summary_on">در جای لمس شده کنش کن</string>
<string name="pref_direct_touch_summary_off">در جای اشاره‌گر کنش کن</string>
<string name="pref_mouse_back">دکمهٔ بازگشت</string>
<string name="pref_mouse_back_action_default">پیش‌گزیده</string>
<string name="pref_mouse_back_action_right">کلیک راست</string>
<string name="pref_mouse_back_action_middle">کلیک وسط</string>
<string name="pref_discovery_autorun">اجرای خودکار</string>
<string name="pref_rediscovery_indicator">کارسازهای کشف شده را برجسته کن</string>
<string name="pref_swipe_sensitivity">حساسیت به کشیدن</string>
<string name="desc_discovery_stop_btn">اکتشاف کارسازها را متوقّف کن</string>
<string name="pref_discovery_autorun_summary">در حالی که در صفحهٔ اصلی هستید، کارسازها را کشف کنید</string>
<string name="title_default_security">امنیت پیش‌گزیده</string>
</resources>

View file

@ -10,94 +10,170 @@
<string name="title_save">Enregistrer</string>
<string name="title_undo">Annuler</string>
<string name="title_settings">Paramètres</string>
<string name="title_credentials_required">Informations d\'identification requises</string>
<string name="title_credentials_required">Se connecter</string>
<string name="title_export">Exporter</string>
<string name="title_import">Importer</string>
<string name="title_view_only_mode">Mode visualisation seulement</string>
<string name="title_view_only_mode">Visualisation uniquement</string>
<string name="title_remember">Mémoriser</string>
<string name="title_hide_remote_cursor">Cacher le curseur à distance</string>
<string name="title_key_compat_mode">Mode de compatibilité des claviers</string>
<string name="title_hide_remote_cursor">Masquer le curseur distant</string>
<string name="title_key_compat_mode">Envoyer des événements claviers rétrocompatibles</string>
<string name="title_unknown_ssh_host">Hôte SSH inconnu</string>
<string name="title_ssh_host_key_changed">La clé de l\'hôte a changé !</string>
<string name="title_ssh_host_key_changed">La clé de l\'hôte SSH a été modifiée</string>
<string name="title_continue">Continuer</string>
<string name="title_delete_servers_before_import">Supprimer les serveurs actuels avant l\'importation</string>
<string name="title_delete_servers_before_import">Supprimer les serveurs courants avant l\'importation</string>
<string name="title_password">Mot de passe</string>
<string name="title_use_repeater">Utiliser un répéteur</string>
<string name="title_github_repo">Dépôt GitHub</string>
<string name="hint_server_name">Nom (par exemple : Ordi travail)</string>
<string name="title_github_repo">Code source</string>
<string name="hint_server_name">Nom (par ex. PC Travail)</string>
<string name="hint_host">Hôte</string>
<string name="hint_repeater_host">Hôte du répéteur</string>
<string name="hint_port">Port</string>
<string name="hint_username">Nom d\'utilisateur</string>
<string name="hint_password">Mot de passe</string>
<string name="hint_key_password">Mot de passe clé</string>
<string name="msg_about_app">AVNC est un client VNC à code source ouvert pour Android. Il vous permet de contrôler à distance tout appareil exécutant un serveur VNC.</string>
<string name="msg_server_profile_added">Serveur ajouté.</string>
<string name="msg_about_app">Un client pour VNC rapide et sécurisé. Visualisez et contrôlez à distance tout appareil exécutant un serveur VNC.
\n
\n
\nAVNC est un logiciel open source et copyleft - réalisé avec l\'aide de nombreux contributeurs.</string>
<string name="msg_server_profile_added">Serveur ajouté</string>
<string name="msg_loading">Chargement…</string>
<string name="msg_imported">Importation réussie</string>
<string name="msg_exported">Exporté avec succès</string>
<string name="msg_export_auth_required">Une authentification est requise avant l\'exportation.</string>
<string name="msg_imported">Importé</string>
<string name="msg_exported">Exporté</string>
<string name="msg_export_auth_required">Authentifiez-vous pour exporter des serveurs</string>
<string name="msg_export_auth_missing">Votre appareil n\'est pas sécurisé. Activez le verrouillage de l\'écran pour permettre l\'authentification avant l\'exportation.</string>
<string name="msg_invalid_key_file">Le fichier n\'est pas une clé privée valide !</string>
<string name="msg_invalid_key_file">Fichier de clé privée invalide</string>
<string name="msg_invalid_vnc_uri">URI VNC invalide</string>
<string name="msg_copied_to_clipboard">Copié !</string>
<string name="desc_navigation_btn">Ouvrir le tiroir de navigation</string>
<string name="desc_settings_btn">Ouvrir les paramètres</string>
<string name="desc_saved_servers_tab">Serveurs sauvegardés</string>
<string name="msg_copied_to_clipboard">Copié</string>
<string name="desc_navigation_btn">Ouvrir le menu de navigation</string>
<string name="desc_settings_btn">Paramètres</string>
<string name="desc_saved_servers_tab">Serveurs enregistrés</string>
<string name="desc_discovered_servers_tab">Serveurs découverts</string>
<string name="desc_add_new_server_btn">Ajouter un nouveau serveur</string>
<string name="desc_save_discovered_server_btn">Sauvegarder ce serveur</string>
<string name="desc_discovery_btn">Démarrer la découverte du serveur</string>
<string name="desc_add_new_server_btn">Ajouter un serveur</string>
<string name="desc_save_discovered_server_btn">Enregistrer</string>
<string name="desc_discovery_btn">Découvrir des serveurs</string>
<string name="desc_discovery_running">Découverte des serveurs sur votre réseau</string>
<string name="desc_keyboard_btn">Ouvrir le clavier</string>
<string name="desc_virtual_keys_btn">Afficher les touches virtuelles</string>
<string name="desc_reset_zoom_btn">Réinitialiser le zoom à 100 %</string>
<string name="desc_retry_connection_btn">Retenter la connexion</string>
<string name="desc_reset_zoom_btn">Réinitialiser le niveau de zoom</string>
<string name="desc_retry_connection_btn">Réessayer</string>
<string name="title_edit">Modifier</string>
<string name="title_duplicate">Dupliquer</string>
<string name="msg_ptr_hiding_not_supported">Le masquage du curseur n\'est pas pris en charge sur cet appareil</string>
<string name="desc_clear_url_btn">Effacer l\'URL</string>
<string name="msg_ptr_hiding_not_supported">Non pris en charge sur cet appareil</string>
<string name="desc_clear_url_btn">Effacer</string>
<string name="pref_gesture_action_none">Aucun</string>
<string name="pref_gesture_action_right_click">Clic droit</string>
<string name="title_open_source_libraries">Bibliothèques à code source ouvert</string>
<string name="pref_tools_summary">Importation/Exportation, Débogage</string>
<string name="title_open_source_libraries">Bibliothèques open source</string>
<string name="pref_tools_summary">Importation/Exportation, déboggage</string>
<string name="pref_gesture_action_double_click">Double clic</string>
<string name="hint_urlbar">Se connecter à un serveur</string>
<string name="pref_input_summary">Gestes, Souris, Clés virtuelles</string>
<string name="pref_input_summary">Gestes, souris, clés virtuelles</string>
<string name="pref_experimental">Expérimental</string>
<string name="title_use_ssh_tunnel">Utiliser le tunnel SSH</string>
<string name="title_use_ssh_tunnel">Utiliser un tunnel SSH</string>
<string name="title_about">À propos</string>
<string name="title_private_key">Clé privée</string>
<string name="title_change">Changer</string>
<string name="title_change">Modifier</string>
<string name="title_license">Licence</string>
<string name="title_report_bug">Signaler une erreur</string>
<string name="title_report_bug">Signaler un problème</string>
<string name="desc_back_btn">Retour</string>
<string name="pref_viewer">Visionneuse</string>
<string name="hint_server_id">Identifiant du serveur</string>
<string name="msg_pip_not_supported">L\'incrustation d\'image n\'est pas prise en charge par cet appareil</string>
<string name="msg_server_profile_deleted">Serveur supprimé.</string>
<string name="msg_key_compat_mode_help">Certains serveurs ne prennent pas en charge la saisie directe en Unicode. Le mode Compatibilité tente d\'envoyer les événements clés dans un format ancien.
\n
\nEssayez d\'activer/désactiver cette fonction si vous avez des difficultés à saisir certains caractères.</string>
<string name="desc_server_url">Entrez l\'adresse du serveur VNC</string>
<string name="desc_help_btn">Afficher l\'aide</string>
<string name="msg_pip_not_supported">Non pris en charge sur cet appareil</string>
<string name="msg_server_profile_deleted">Serveur supprimé</string>
<string name="msg_key_compat_mode_help">Certains serveurs ont une prise en charge limitée du format Unicode. Les événements de saisie rétrocompatibles peuvent aider à saisir du texte non anglais.</string>
<string name="desc_server_url">Adresse du serveur</string>
<string name="desc_help_btn">Aide</string>
<string name="desc_discovered_list">Liste des serveurs découverts automatiquement</string>
<string name="desc_saved_servers_list">Liste des serveurs enregistrés</string>
<string name="pref_ui">Interface</string>
<string name="pref_ui_summary">Apparence et ressenti</string>
<string name="pref_viewer_summary">Image dans l\'image, Zoom</string>
<string name="pref_server_summary">Découverte, Références</string>
<string name="pref_viewer_summary">Picture-in-picture, Zoom</string>
<string name="pref_server_summary">Connexion, découverte</string>
<string name="pref_input">Entrée</string>
<string name="pref_servers">Serveurs</string>
<string name="pref_experimental_summary">Des friandises supplémentaires !</string>
<string name="pref_experimental_summary">Bonus</string>
<string name="pref_tools">Outils</string>
<string name="pref_gesture_action_left_click">Clic gauche</string>
<string name="pref_gesture_action_middle_click">Clic du milieu</string>
<string name="pref_theme">Thème</string>
<string name="tip_empty_server_list">La liste des serveurs est vide.
\nUtilisez le bouton « <b>+</b> » ; pour ajouter un serveur, ou
\nUtilisez la barre d\'outils supérieure pour vous connecter directement à un serveur.</string>
\nCliquez sur « <b>+</b> » ; pour ajouter un serveur, ou
\nUtilisez la barre d\'adresse supérieure pour vous connecter directement.</string>
<string name="pref_experimental_immersive_mode">Mode immersif complet</string>
<string name="pref_experimental_toolbar_swipe_close">Balayer pour fermer le tiroir de la barre d\'outils</string>
<string name="pref_experimental_toolbar_swipe_close">Faîtes glisser pour fermer le menu de la barre d\'outils</string>
<string name="pref_experimental_immersive_mode_summary">Masquer toutes les barres du système en mode plein écran</string>
<string name="pref_theme_option_system">Système</string>
<string name="pref_swipe2">Glissement à deux doigts</string>
<string name="pref_double_tap">Appui double</string>
<string name="pref_long_press">Appui long</string>
<string name="pref_swipe1">Glissement à un doigt</string>
<string name="pref_fullscreen">Plein écran</string>
<string name="pref_km_back_to_escape">Retour → Échap</string>
<string name="pref_km_back_to_escape_summary">N\'affecte pas la barre de navigation</string>
<string name="pref_toolbar_alignment_option_start">Début</string>
<string name="pref_toolbar_alignment">Alignement</string>
<string name="pref_toolbar_alignment_option_end">Fin</string>
<string name="pref_zoom">Zoom</string>
<string name="tip_urlbar">Vous pouvez saisir une adresse IP, un nom d\'hôte ou une URI « vnc:// ».</string>
<string name="tip_discovery">Le mode découverte peut trouver automatiquement les serveurs pris en charge
\ns\'exécutant sur votre réseau.</string>
<string name="pref_sort_server_list">Trier les serveurs enregistrés par nom</string>
<string name="pref_gesture">Gestes</string>
<string name="pref_single_tap">Appui simple</string>
<string name="pref_two_finger_tap">Appui à deux doigts</string>
<string name="pref_gesture_action_move_pointer">Déplacer le curseur</string>
<string name="pref_gesture_action_remote_scroll">Faire défiler le contenu distant</string>
<string name="pref_gesture_action_remote_drag">Faire glisser le contenu distant</string>
<string name="pref_gesture_action_open_keyboard">Ouvrir le clavier</string>
<string name="pref_prefer_advanced_editor">Ouvrir directement les options avancées du serveur</string>
<string name="pref_logs">Journaux</string>
<string name="pref_experimental_toolbar_swipe_close_summary">Au cours d\'une session distante, vous pouvez faire glisser pour fermer la barre d\'outils ouverte</string>
<string name="pref_enable_pip">Picture-in-picture</string>
<string name="pref_toolbar">Barre d\'outils</string>
<string name="pref_direct_touch_summary_on">Effectuer les actions au point de contact</string>
<string name="pref_direct_touch">Pointage direct</string>
<string name="pref_direct_touch_summary_off">Effectuer les actions au niveau du pointeur</string>
<string name="pref_debugging">Débogage</string>
<string name="title_clear">Effacer</string>
<string name="pref_theme_option_dark">Sombre</string>
<string name="pref_theme_option_light">Clair</string>
<string name="pref_hide_local_cursor">Masquer le pointeur local</string>
<string name="pref_vk">Touches virtuelles</string>
<string name="pref_vk_show_all">Afficher toutes les touches</string>
<string name="pref_mouse">Souris</string>
<string name="pref_mouse_passthrough_summary_off">Utiliser les événements de la souris pour les gestes locaux</string>
<string name="pref_mouse_passthrough_summary_on">Envoyer les événements de la souris directement au serveur</string>
<string name="pref_vk_open_with_keyboard">Ouvrir avec le clavier</string>
<string name="pref_km">Association des touches</string>
<string name="pref_km_right_alt_to_super">Alt Droite → Super</string>
<string name="pref_zoom_min">Minimum</string>
<string name="pref_zoom_max">Maximum</string>
<string name="pref_credential">Connexion</string>
<string name="pref_cred_autocomplete">Autocomplétion</string>
<string name="pref_cred_autocomplete_summary">Suggérer les identifiants enregistrés lors de la connexion à de nouveaux serveurs</string>
<string name="pref_discovery">Découverte</string>
<string name="pref_clipboard_sync">Synchronisation du presse-papiers</string>
<string name="pref_drag">Glisser</string>
<string name="pref_km_language_switch_to_super">Changement de langue → Super</string>
<string name="pref_import_export">Importer/Exporter des serveurs</string>
<string name="pref_keycode_map">Carte des codes-clés</string>
<string name="pref_mouse_passthrough">Passage de la souris</string>
<string name="pref_discovery_autorun_summary">Découverte des serveurs sur la page d\'accueil</string>
<string name="desc_discovery_stop_btn">Arrêter la découverte des serveurs</string>
<string name="pref_rediscovery_indicator">Mettre en avant les serveurs découverts de nouveau</string>
<string name="pref_discovery_autorun">Exécution automatique</string>
<string name="pref_gesture_action_pan">Cadre local panoramique</string>
<string name="pref_mouse_back">Bouton retour</string>
<string name="pref_mouse_back_action_default">Par défaut</string>
<string name="pref_mouse_back_action_middle">Clic molette</string>
<string name="pref_mouse_back_action_right">Clic droit</string>
<string name="title_default_security">Sécurité par défaut</string>
<string name="pref_swipe_sensitivity">Sensibilité du défilement</string>
<string name="msg_drag_gesture_help">L\'affectation d\'une action à un geste de glissement modifiera la détection de l\'appui long :
\n
\n<b>Appui maintenu</b> → Appui long.
\n<b>Appui-maintien-glissement</b> → Glissement</string>
<string name="pref_orientation">Orientation de l\'écran</string>
<string name="pref_orientation_option_auto">Automatique</string>
<string name="pref_orientation_option_portrait">Portrait</string>
<string name="pref_orientation_option_landscape">Paysage</string>
</resources>

View file

@ -74,12 +74,9 @@
<string name="pref_clipboard_sync">Utklippstavlesynkronisering</string>
<string name="pref_credential">Innlogging</string>
<string name="pref_discovery">Oppdagelse</string>
<string name="pref_discovery_auto_start">Automatisk start</string>
<string name="pref_discovery_auto_start_summary">Oppdag tjenere når programmet startes</string>
<string name="title_import">Importer</string>
<string name="pref_experimental_immersive_mode">Helt oppslukende modus</string>
<string name="pref_experimental_immersive_mode_summary">Skjuler alle systemfelter i fullskjermsmodus</string>
<string name="pref_discovery_restart_summary">Kontinuerlig oppdagelse</string>
<string name="desc_add_new_server_btn">Legg til tjener</string>
<string name="title_copy_name">Kopier navn</string>
<string name="title_export">Eksporter</string>
@ -98,8 +95,6 @@
<string name="pref_experimental_summary">Ekstra godsaker</string>
<string name="pref_gesture_action_none">Ingen</string>
<string name="pref_gesture_action_double_click">Dobbeltklikk</string>
<string name="pref_discovery_timeout">Tidsavbrudd (sek)</string>
<string name="pref_discovery_restart">Omstart</string>
<string name="pref_vk_show_all">Vis alle taster</string>
<string name="msg_invalid_vnc_uri">Ugyldig VNC-URI</string>
<string name="desc_reset_zoom_btn">Tilbakestill forstørrelse</string>
@ -134,11 +129,9 @@
<string name="pref_km_back_to_escape_summary">Har ingen innvirkning på navigasjonsfeltet</string>
<string name="pref_toolbar_alignment_option_start">Start</string>
<string name="pref_cred_autocomplete_summary">Foreslå kjente innlogginger ved tilkobling til nye tjenere</string>
<string name="pref_discovery_restart_delay">Omstartsforsinkelse (min)</string>
<string name="pref_import_export">Importer/eksporter tjenere</string>
<string name="pref_debugging">Avlusing</string>
<string name="pref_logs">Logger</string>
<string name="pref_experimental_indicator">Vis oppdagede tjenere igjen</string>
<string name="msg_about_app">En rask og sikker VNC-klient. Koble til alle enheter som har en VNC-tjener.
\n
\n
@ -159,9 +152,20 @@
\neller feltet ovenfor for å koble til direkte.</string>
<string name="pref_km_back_to_escape">Tilbake → Esc</string>
<string name="title_use_repeater">Bruk gjentaker</string>
<string name="pref_experimental_indicator_summary">Framhever treff blandt allerede lagrede tjenere under oppdagelse.
\nKrever programomstart.</string>
<string name="tip_discovery">Oppdagelse kan automatisk finne støttede tjenere
\npå nettverket ditt.</string>
<string name="msg_export_auth_missing">Skru på skjermlås for å kreve identitetsbekreftelse før eksport.</string>
<string name="pref_direct_touch">Direkte-trykk</string>
<string name="pref_direct_touch_summary_on">Utfør handlinger ved berøring</string>
<string name="pref_direct_touch_summary_off">Utfør handlinger ved peker</string>
<string name="pref_rediscovery_indicator">Framhev tjenere som oppdages igjen</string>
<string name="desc_discovery_stop_btn">Stopp oppdaging av tjenere</string>
<string name="pref_discovery_autorun">Kjør automatisk</string>
<string name="pref_discovery_autorun_summary">Oppdag tjenere når hovedsiden vises</string>
<string name="pref_mouse_back">«Tilbake»-knapp</string>
<string name="pref_mouse_back_action_default">Forvalg</string>
<string name="pref_mouse_back_action_right">Høyreklikk</string>
<string name="pref_mouse_back_action_middle">Midt-klikk</string>
<string name="title_default_security">Forvalgt sikkerhet</string>
<string name="pref_swipe_sensitivity">Dragningssensitivitet</string>
</resources>

View file

@ -0,0 +1,177 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="title_cancel">Anuluj</string>
<string name="title_password">Hasło</string>
<string name="title_add_server_profile">Dodaj serwer</string>
<string name="msg_key_compat_mode_help">Niektóre serwery mają ograniczoną obsługę Unicode. Kluczowe zdarzenia starszego typu mogą pomóc w wprowadzaniu tekstu w języku innym niż angielski.</string>
<string name="title_delete">Usuń</string>
<string name="title_clear">Wyczyść</string>
<string name="title_copy_name">Nazwa kopii</string>
<string name="title_advanced">Zaawansowane</string>
<string name="title_save">Zapisz</string>
<string name="title_undo">Cofnij</string>
<string name="title_settings">Ustawienia</string>
<string name="title_view_only_mode">Tylko podgląd</string>
<string name="title_use_ssh_tunnel">Użyj tunelu SSH</string>
<string name="title_private_key">Klucz prywatny</string>
<string name="title_about">O alikacji</string>
<string name="title_license">Licencja</string>
<string name="title_open_source_libraries">Biblioteki open source</string>
<string name="title_report_bug">Zgłoś problem</string>
<string name="title_github_repo">Kod źródłowy</string>
<string name="hint_urlbar">Połącz się z serwerem</string>
<string name="hint_server_name">Nazwa (np. Komputer służbowy)</string>
<string name="hint_host">Host</string>
<string name="hint_port">Port</string>
<string name="hint_server_id">Identyfikator serwera</string>
<string name="hint_username">Nazwa użytkownika</string>
<string name="hint_password">Hasło</string>
<string name="msg_about_app">Szybki i bezpieczny klient VNC. Umożliwia zdalne wyświetlanie i kontrolowanie dowolnego urządzenia z serwerem VNC.
\n
\n
\nAVNC jest oprogramowaniem typu open source i copyleft - powstał dzięki pomocy wielu współpracowników.</string>
<string name="msg_pip_not_supported">Brak wsparcia dla tego urządzenia</string>
<string name="msg_loading">Ładowanie…</string>
<string name="msg_invalid_key_file">Nieprawidłowy plik klucza prywatnego</string>
<string name="msg_invalid_vnc_uri">Nieprawidłowy identyfikator URI VNC</string>
<string name="msg_copied_to_clipboard">Skopiowano</string>
<string name="tip_empty_server_list">Lista serwerów jest pusta.
\nKliknij przycisk \'<b>+</b>\', aby dodać serwer, lub
\nużyj górnego paska adresu, aby połączyć się bezpośrednio.</string>
<string name="tip_urlbar">Można wprowadzić adres IP, nazwę hosta lub \'vnc://\' URI.</string>
<string name="desc_navigation_btn">Otwórz szufladę nawigacyjną</string>
<string name="desc_settings_btn">Ustawienia</string>
<string name="desc_saved_servers_tab">Zapisane serwery</string>
<string name="desc_discovered_servers_tab">Wykryte serwery</string>
<string name="title_edit_server_profile">Edytuj serwer</string>
<string name="title_unknown_ssh_host">Nieznany host SSH</string>
<string name="title_ssh_host_key_changed">Zmieniono klucz hosta SSH</string>
<string name="title_delete_servers_before_import">Usuń bieżące serwery przed importem</string>
<string name="msg_ptr_hiding_not_supported">Brak wsparcia dla tego urządzenia</string>
<string name="title_continue">Kontynuuj</string>
<string name="title_default_security">Domyślne zabezpieczenie</string>
<string name="desc_reset_zoom_btn">Zresetuj powiększenie</string>
<string name="desc_retry_connection_btn">Ponów próbę</string>
<string name="desc_clear_url_btn">Wyczyść</string>
<string name="desc_help_btn">Pomoc</string>
<string name="desc_discovered_list">Lista automatycznie wykrytych serwerów</string>
<string name="desc_saved_servers_list">Lista zapisanych serwerów</string>
<string name="pref_ui">Interfejs</string>
<string name="pref_viewer_summary">Obraz w obrazie, zoom</string>
<string name="pref_input">Wejście</string>
<string name="pref_input_summary">Gesty, mysz, wirtualna klawiatura</string>
<string name="pref_servers">Serwery</string>
<string name="pref_server_summary">Logowanie, odkrywanie</string>
<string name="pref_tools">Narzędzia</string>
<string name="pref_tools_summary">Import/Eksport, debugowanie</string>
<string name="pref_experimental">Eksperymentalne</string>
<string name="pref_experimental_summary">Dodatkowe gadżety</string>
<string name="pref_gesture_action_none">Żaden</string>
<string name="pref_gesture_action_middle_click">Środkowy przycisk</string>
<string name="pref_gesture_action_left_click">Lewy przycisk</string>
<string name="pref_gesture_action_right_click">Prawy przycisk</string>
<string name="pref_gesture_action_double_click">Podwójne kliknięcie</string>
<string name="pref_gesture_action_move_pointer">Przesuń wskaźnik</string>
<string name="pref_gesture_action_pan">Przesuń ramkę lokalną</string>
<string name="pref_gesture_action_open_keyboard">Otwórz klawiaturę</string>
<string name="pref_theme">Motyw</string>
<string name="pref_theme_option_system">System</string>
<string name="pref_theme_option_dark">Ciemny</string>
<string name="pref_theme_option_light">Jasny</string>
<string name="pref_prefer_advanced_editor">Bezpośrednie otwieranie zaawansowanych opcji serwera</string>
<string name="pref_sort_server_list">Sortuj zapisane serwery według nazwy</string>
<string name="pref_gesture">Gesty</string>
<string name="pref_swipe1">Przesunięcie jednym palcem</string>
<string name="pref_swipe2">Przesunięcie dwoma palcami</string>
<string name="pref_drag">Przeciągnij</string>
<string name="pref_swipe_sensitivity">Czułość przesuwania</string>
<string name="pref_mouse_passthrough_summary_on">Wysyłanie zdarzeń myszy bezpośrednio do serwera</string>
<string name="pref_hide_local_cursor">Ukryj wskaźnik lokalny</string>
<string name="pref_mouse_back">Przycisk Wstecz</string>
<string name="pref_mouse_back_action_default">Domyślnie</string>
<string name="pref_km_back_to_escape_summary">Nie wpływa na pasek nawigacyjny</string>
<string name="pref_enable_pip">Obraz w obrazie</string>
<string name="pref_toolbar">Pasek narzędzi</string>
<string name="pref_toolbar_alignment_option_start">Start</string>
<string name="pref_zoom">Powiększenie</string>
<string name="pref_zoom_min">Minimum</string>
<string name="pref_discovery_autorun">Automatyczne uruchamianie</string>
<string name="pref_rediscovery_indicator">Wyróżnij odkryte ponownie serwery</string>
<string name="pref_debugging">Debugowanie</string>
<string name="pref_experimental_toolbar_swipe_close">Przesuń, aby zamknąć szufladę paska narzędzi</string>
<string name="pref_experimental_immersive_mode_summary">Ukrywa wszystkie paski systemowe w trybie pełnoekranowym</string>
<string name="desc_discovery_stop_btn">Zatrzymaj wykrywanie serwerów</string>
<string name="desc_virtual_keys_btn">Pokaż wirtualną klawiaturę</string>
<string name="desc_discovery_running">Wykrywanie serwerów w sieci</string>
<string name="desc_keyboard_btn">Otwórz klawiaturę</string>
<string name="desc_server_url">Adres serwera</string>
<string name="pref_ui_summary">Wygląd i działanie</string>
<string name="pref_viewer">Przeglądarka</string>
<string name="pref_mouse">Mysz</string>
<string name="pref_single_tap">Pojedyncze dotknięcie</string>
<string name="pref_double_tap">Dwukrotne stuknięcie</string>
<string name="pref_two_finger_tap">Stuknięcie dwoma palcami</string>
<string name="pref_long_press">Długie naciśnięcie</string>
<string name="pref_fullscreen">Pełny ekran</string>
<string name="pref_credential">Logowanie</string>
<string name="pref_cred_autocomplete">Autouzupełnianie</string>
<string name="pref_zoom_max">Maximum</string>
<string name="pref_clipboard_sync">Synchronizacja schowka</string>
<string name="pref_logs">Dzienniki</string>
<string name="pref_cred_autocomplete_summary">Sugeruj zapisywanie loginów przy łączeniu się z nowymi serwerami</string>
<string name="pref_experimental_toolbar_swipe_close_summary">Podczas sesji zdalnej możesz przesunąć palcem, aby zamknąć aktualnie otwarty pasek narzędzi</string>
<string name="title_edit">Edytuj</string>
<string name="title_duplicate">Duplikuj</string>
<string name="title_copy_host">Kopiuj hosta</string>
<string name="title_credentials_required">Logowanie</string>
<string name="title_remember">Zapamiętaj</string>
<string name="title_export">Eksportuj</string>
<string name="title_import">Importuj</string>
<string name="title_change">Zmień</string>
<string name="hint_key_password">Hasło klucza</string>
<string name="msg_server_profile_added">Dodano serwer</string>
<string name="msg_server_profile_deleted">Serwer usunięto</string>
<string name="msg_imported">Importowane</string>
<string name="msg_exported">Eksportowane</string>
<string name="msg_export_auth_missing">Twoje urządzenie nie jest zabezpieczone. Włącz blokadę ekranu, aby umożliwić uwierzytelnianie przed eksportem.</string>
<string name="tip_discovery">Funkcja Discovery może automatycznie znaleźć obsługiwane serwery
\ndziałające w sieci.</string>
<string name="desc_add_new_server_btn">Dodaj serwer</string>
<string name="desc_save_discovered_server_btn">Zapisz</string>
<string name="desc_back_btn">Wstecz</string>
<string name="pref_mouse_passthrough">Przekazywanie myszy</string>
<string name="pref_mouse_passthrough_summary_off">Używanie zdarzeń myszy do wykonywania gestów lokalnych</string>
<string name="pref_gesture_action_remote_scroll">Przewiń zdalną zawartość</string>
<string name="pref_mouse_back_action_middle">Kliknij środkowym przyciskiem</string>
<string name="pref_vk_show_all">Pokaż wszystkie klawisze</string>
<string name="pref_km">Mapowanie klawiszy</string>
<string name="pref_km_right_alt_to_super">Prawy Alt → Super</string>
<string name="pref_km_back_to_escape">Powrót → Escape</string>
<string name="pref_toolbar_alignment">Wyrównanie</string>
<string name="pref_toolbar_alignment_option_end">Koniec</string>
<string name="pref_discovery_autorun_summary">Odkryj serwery na stronie głównej</string>
<string name="pref_discovery">Odkrywanie</string>
<string name="pref_import_export">Importuj/Eksportuj serwery</string>
<string name="pref_keycode_map">Mapa kodów</string>
<string name="pref_direct_touch">Bezpośredni dotyk</string>
<string name="pref_direct_touch_summary_on">Wykonuj działania w punkcie kontaktu</string>
<string name="pref_mouse_back_action_right">Kliknij prawym przyciskiem</string>
<string name="pref_gesture_action_remote_drag">Przeciągnij zdalną zawartość</string>
<string name="pref_vk">Wirtualna klawiatura</string>
<string name="pref_vk_open_with_keyboard">Otwórz za pomocą klawiatury</string>
<string name="title_use_repeater">Użyj wzmacniacza</string>
<string name="title_hide_remote_cursor">Ukryj zdalny wskaźnik</string>
<string name="pref_direct_touch_summary_off">Wykonaj działania na wskaźniku</string>
<string name="pref_km_language_switch_to_super">Zmiana języka → Super</string>
<string name="pref_experimental_immersive_mode">Tryb pełnej immersji</string>
<string name="msg_drag_gesture_help">Przypisanie akcji do gestu przeciągania zmieni Wykrywanie długiego naciśnięcia:
\n
\n<b>Wciśnięcie i zwolnienie </b> → Długie naciśnięcie
\n<b>Naciśnij-przytrzymaj-przeciągnij</b> → Przeciągnij</string>
<string name="pref_orientation">Orientacja ekranu</string>
<string name="pref_orientation_option_auto">Automatycznie</string>
<string name="pref_orientation_option_portrait">Portret</string>
<string name="pref_orientation_option_landscape">Krajobraz</string>
<string name="desc_discovery_btn">Odnajduj serwery</string>
<string name="msg_export_auth_required">Uwierzytelnij się, aby eksportować serwery</string>
</resources>

View file

@ -6,11 +6,11 @@
~
~ See COPYING.txt for more details.
--><resources>
<string name="title_add_server_profile">Adicionar um servidor</string>
<string name="title_edit_server_profile">Edição do servidor</string>
<string name="title_add_server_profile">Adicionar servidor</string>
<string name="title_edit_server_profile">Editar servidor</string>
<string name="title_duplicate">Duplicar</string>
<string name="title_edit">Editar</string>
<string name="title_delete">Excluir</string>
<string name="title_delete">Apagar</string>
<string name="title_copy_host">Copiar host</string>
<string name="title_copy_name">Copiar nome</string>
<string name="title_advanced">Avançado</string>
@ -18,27 +18,27 @@
<string name="title_save">Salvar</string>
<string name="title_undo">Desfazer</string>
<string name="title_settings">Configurações</string>
<string name="title_credentials_required">Credenciais necessárias</string>
<string name="title_remember">Lembrar</string>
<string name="title_credentials_required">Autenticação</string>
<string name="title_remember">Salvar</string>
<string name="title_export">Exportar</string>
<string name="title_import">Importar</string>
<string name="title_view_only_mode">Modo somente de visualização</string>
<string name="title_hide_remote_cursor">Esconder cursor remoto</string>
<string name="title_key_compat_mode">Modo de compatibilidade do teclado</string>
<string name="title_use_ssh_tunnel">Usar túnel de SSH</string>
<string name="title_unknown_ssh_host">Host de SSH desconhecido</string>
<string name="title_ssh_host_key_changed">A chave do host foi alterada</string>
<string name="title_view_only_mode">Somente visualização</string>
<string name="title_hide_remote_cursor">Esconder ponteiro remoto</string>
<string name="title_key_compat_mode">Enviar eventos de teclado compatíveis</string>
<string name="title_use_ssh_tunnel">Usar túnel SSH</string>
<string name="title_unknown_ssh_host">Host SSH desconhecido</string>
<string name="title_ssh_host_key_changed">Chave do host de SSH mudou</string>
<string name="title_continue">Continuar</string>
<string name="title_delete_servers_before_import">Excluir os servidores atuais antes de importar</string>
<string name="title_delete_servers_before_import">Apagar os servidores atuais antes de importar</string>
<string name="title_password">Senha</string>
<string name="title_use_repeater">Usar repetidor</string>
<string name="title_private_key">Chave privada</string>
<string name="title_change">Alterar</string>
<string name="title_change">Mudar</string>
<string name="title_about">Sobre</string>
<string name="title_license">Licença</string>
<string name="title_open_source_libraries">Bibliotecas de código livre</string>
<string name="title_report_bug">Relatar um erro</string>
<string name="title_github_repo">Repositório no Github</string>
<string name="title_report_bug">Relatar um problema</string>
<string name="title_github_repo">Código fonte</string>
<string name="hint_urlbar">Conectar ao servidor</string>
<string name="hint_server_name">Nome (ex. PC do trabalho)</string>
<string name="hint_host">Host</string>
@ -48,50 +48,56 @@
<string name="hint_username">Nome de usuário</string>
<string name="hint_password">Senha</string>
<string name="hint_key_password">Senha chave</string>
<string name="msg_about_app">O AVNC é um cliente VNC de software livre para Android. Permite o controle remoto de qualquer dispositivo que executa um servidor VNC.</string>
<string name="msg_about_app">Um cliente VNC rápido e seguro. Permite visualizar e controlar remotamente qualquer dispositivo que executa um servidor VNC.
\n
\n
\nO AVNC é de código aberto e sob licença copyleft — feito com a ajuda de muitos colaboradores.</string>
<string name="msg_server_profile_added">Servidor adicionado</string>
<string name="msg_server_profile_deleted">Servidor excluído</string>
<string name="msg_pip_not_supported">O recurso de Picture-in-Picture não é suportado neste dispositivo</string>
<string name="msg_loading">Carregando …</string>
<string name="msg_imported">Importado com sucesso</string>
<string name="msg_exported">Exportado com sucesso</string>
<string name="msg_export_auth_required">É necessária a autenticação antes de exportar</string>
<string name="msg_server_profile_deleted">Servidor apagado</string>
<string name="msg_pip_not_supported">Não suportado neste dispositivo</string>
<string name="msg_loading">Carregando…</string>
<string name="msg_imported">Importado</string>
<string name="msg_exported">Exportado</string>
<string name="msg_export_auth_required">Autenticar primeiro para poder exportar servidores</string>
<string name="msg_export_auth_missing">Seu dispositivo não está seguro. Ative o bloqueio da tela para permitir a autenticação antes de exportar.</string>
<string name="msg_invalid_key_file">O arquivo não é uma chave privada válida</string>
<string name="msg_invalid_vnc_uri">O URI do VNC inválido</string>
<string name="msg_invalid_key_file">Arquivo de chave privada inválido</string>
<string name="msg_invalid_vnc_uri">A URI do VNC inválida</string>
<string name="msg_copied_to_clipboard">Copiado</string>
<string name="msg_key_compat_mode_help">Alguns servidores não tem suporte a entrada direta do Unicode. O modo de compatibilidade tenta enviar eventos chave em formato legado. \n\nTente ativar/desativar isto, se você estiver tendo problemas inserindo alguns caracteres.</string>
<string name="tip_empty_server_list">A lista de servidores está vazia.\nUse o botão \'<b>+</b>\' para adicionar um servidor, ou\nUse a barra do Url na parte superior para conectar-se diretamente a um servidor.</string>
<string name="tip_urlbar">Você pode digitar o nome de host ou endereço de IP.\n\'vnc://\' URIs também são suportados.</string>
<string name="tip_discovery">A descoberta pode encontrar automaticamente os servidores suportados\nexecutados na sua rede.</string>
<string name="msg_key_compat_mode_help">Alguns servidores têm suporte limitado ao Unicode. Os eventos de teclado legado podem ajudar a inserir texto não-inglês.</string>
<string name="tip_empty_server_list">A lista de servidores está vazia.
\nUse o botão \'<b>+</b>\' para adicionar um servidor, ou
\nuse a barra de endereço na área superior para conectar diretamente.</string>
<string name="tip_urlbar">Você pode digitar o endereço de IP, nome de host ou \'vnc://\' URI.</string>
<string name="tip_discovery">A descoberta pode automaticamente encontrar os servidores suportados
\nexecutados na sua rede.</string>
<!-- Accessibility descriptions -->
<string name="desc_navigation_btn">Abrir a aba de navegação</string>
<string name="desc_settings_btn">Abrir configurações</string>
<string name="desc_navigation_btn">Abrir aba de navegação</string>
<string name="desc_settings_btn">Configurações</string>
<string name="desc_saved_servers_tab">Servidores salvos</string>
<string name="desc_discovered_servers_tab">Servidores encontrados</string>
<string name="desc_add_new_server_btn">Adicionar um novo servidor</string>
<string name="desc_save_discovered_server_btn">Salvar este servidor</string>
<string name="desc_discovery_btn">Iniciar a descoberta de servidores</string>
<string name="desc_add_new_server_btn">Adicionar servidor</string>
<string name="desc_save_discovered_server_btn">Salvar</string>
<string name="desc_discovery_btn">Descobrir servidores</string>
<string name="desc_discovery_running">Descobrindo servidores na sua rede</string>
<string name="desc_keyboard_btn">Mostrar teclado</string>
<string name="desc_virtual_keys_btn">Mostrar teclas virtuais</string>
<string name="desc_reset_zoom_btn">Resetar o zoom para 100%</string>
<string name="desc_retry_connection_btn">Tente conectar novamente</string>
<string name="desc_reset_zoom_btn">Redefinir zoom</string>
<string name="desc_retry_connection_btn">Tente novamente</string>
<string name="desc_back_btn">Voltar</string>
<string name="desc_clear_url_btn">Remover a URL</string>
<string name="desc_server_url">Digite o endereço do servidor VNC</string>
<string name="desc_help_btn">Mostrar ajuda</string>
<string name="desc_discovered_list">Lista de servidores descobertos automaticamente</string>
<string name="desc_saved_servers_list">Lista de servidores salvos</string>
<string name="desc_clear_url_btn">Limpar</string>
<string name="desc_server_url">Endereço do servidor</string>
<string name="desc_help_btn">Ajuda</string>
<string name="desc_discovered_list">Lista dos servidores descobertos automaticamente</string>
<string name="desc_saved_servers_list">Lista dos servidores salvos</string>
<!-- Preference -->
<string name="pref_ui">Interface</string>
<string name="pref_ui_summary">Aspecto</string>
<string name="pref_viewer">Visualizador</string>
<string name="pref_viewer_summary">Picture-in-picture, zoom</string>
<string name="pref_viewer_summary">Imagem em popup, zoom</string>
<string name="pref_input">Entrada</string>
<string name="pref_input_summary">Gestos, mouse, teclas virtuais</string>
<string name="pref_servers">Servidores</string>
<string name="pref_server_summary">Descoberta, Credenciais</string>
<string name="pref_server_summary">Autenticação, descoberta</string>
<string name="pref_tools">Ferramentas</string>
<string name="pref_tools_summary">Importar/Exportar, depuração</string>
<string name="pref_experimental">Experimental</string>
@ -107,8 +113,8 @@
<string name="pref_gesture_action_remote_drag">Arrastar conteúdo remoto</string>
<string name="pref_gesture_action_open_keyboard">Mostrar teclado</string>
<string name="pref_theme">Tema</string>
<string name="pref_prefer_advanced_editor">Preferência do editor avançado</string>
<string name="pref_sort_server_list">Ordenar a lista de servidores</string>
<string name="pref_prefer_advanced_editor">Abrir diretamente opções avançadas de servidor</string>
<string name="pref_sort_server_list">Ordenar servidores salvos pelo nome</string>
<string name="pref_gesture">Gestos</string>
<string name="pref_single_tap">Toque único</string>
<string name="pref_two_finger_tap">Toque com 2 dedos</string>
@ -116,54 +122,66 @@
<string name="pref_long_press">Toque longo</string>
<string name="pref_swipe1">Deslize com 1 dedo</string>
<string name="pref_swipe2">Deslize com 2 dedos</string>
<string name="pref_drag">Arrastar (Toque longo depois deslizar)</string>
<string name="pref_drag">Arrastar</string>
<string name="pref_mouse">Mouse</string>
<string name="pref_mouse_passthrough">Passagem do mouse</string>
<string name="pref_mouse_passthrough_summary_off">Eventos do mouse são usados localmente para gestos</string>
<string name="pref_mouse_passthrough_summary_on">Eventos do mouse são enviados diretamente para servidor</string>
<string name="pref_mouse_passthrough_summary_off">Use eventos do mouse para gestos locais</string>
<string name="pref_mouse_passthrough_summary_on">Enviar eventos do mouse diretamente para servidor</string>
<string name="pref_vk">Teclas virtuais</string>
<string name="pref_vk_show_all">Mostrar todas teclas</string>
<string name="pref_vk_open_with_keyboard">Mostrar quando teclado está disponível</string>
<string name="pref_vk_open_with_keyboard">Abrir com teclado</string>
<string name="pref_km">Mapeamento de teclas</string>
<string name="pref_km_right_alt_to_super">Right Alt → Super</string>
<string name="pref_km_language_switch_to_super">Alteração de idioma → Super</string>
<string name="pref_km_back_to_escape">Voltar → Escape</string>
<string name="pref_km_back_to_escape_summary">A barra de navegação não é afetada por isto</string>
<string name="pref_fullscreen">Usar tela completa</string>
<string name="pref_enable_pip">Picture-in-picture</string>
<string name="pref_km_back_to_escape_summary">Não afeta a barra de navegação</string>
<string name="pref_fullscreen">Tela cheia</string>
<string name="pref_enable_pip">Imagem em popup</string>
<string name="pref_toolbar">Barra de ferramentas</string>
<string name="pref_toolbar_alignment">Alinhamento</string>
<string name="pref_zoom">Zoom</string>
<string name="pref_zoom_min">Zoom mínimo</string>
<string name="pref_zoom_max">Zoom máximo</string>
<string name="pref_clipboard_sync">Sincronização do Clipboard</string>
<string name="pref_credential">Credenciais</string>
<string name="pref_cred_autocomplete">Ativar preenchimento automático</string>
<string name="pref_cred_autocomplete_summary">Sugerir credenciais conhecidas ao conectar-se a um novo servidor</string>
<string name="pref_zoom_min">Mínimo</string>
<string name="pref_zoom_max">Máximo</string>
<string name="pref_clipboard_sync">Sincronização do clipboard</string>
<string name="pref_credential">Autenticação</string>
<string name="pref_cred_autocomplete">Preenchimento automático</string>
<string name="pref_cred_autocomplete_summary">Sugerir sessões salvas ao conectar para novos servidores</string>
<string name="pref_discovery">Descoberta</string>
<string name="pref_discovery_timeout">Intervalo (em segundos)</string>
<string name="pref_discovery_auto_start">Iniciar automaticamente</string>
<string name="pref_discovery_auto_start_summary">Ao abrir app iniciar a Descoberta</string>
<string name="pref_discovery_restart">Reiniciar</string>
<string name="pref_discovery_restart_summary">Reiniciar a Descoberta depois de parar</string>
<string name="pref_discovery_restart_delay">Resetar adiamento (minutos)</string>
<string name="pref_import_export">Importar/Exportar servidores</string>
<string name="pref_debugging">Depuração</string>
<string name="pref_logs">Registros</string>
<string name="pref_keycode_map">Mapa de códigos de teclado</string>
<string name="pref_experimental_toolbar_swipe_close">Deslizar para fechar a barra de ferramentas</string>
<string name="pref_experimental_toolbar_swipe_close_summary">Durante uma sessão remota, você pode deslizar para fechar a barra de ferramentas atualmente aberta</string>
<string name="pref_experimental_indicator">Indicador de descoberta para servidores salvos</string>
<string name="pref_experimental_indicator_summary">Destacar os servidores já salvos que também foram encontrados pela Descoberta.
\nRequer reinício do app.</string>
<string name="pref_theme_option_system">Seguir Tema do Sistema</string>
<string name="pref_theme_option_dark">Modo Escuro</string>
<string name="pref_theme_option_light">Modo Claro</string>
<string name="pref_experimental_toolbar_swipe_close">Deslize para fechar a barra de ferramentas</string>
<string name="pref_experimental_toolbar_swipe_close_summary">Durante uma sessão remota, você pode deslizar para fechar a barra de ferramentas aberta neste momento</string>
<string name="pref_theme_option_system">Sistema</string>
<string name="pref_theme_option_dark">Escuro</string>
<string name="pref_theme_option_light">Claro</string>
<string name="pref_toolbar_alignment_option_start">Início</string>
<string name="pref_toolbar_alignment_option_end">Final</string>
<string name="pref_hide_local_cursor">Ocultar Cursor do Android</string>
<string name="pref_hide_local_cursor">Esconder ponteiro local</string>
<string name="title_clear">Limpar</string>
<string name="pref_experimental_immersive_mode">Modo totalmente imersivo</string>
<string name="pref_experimental_immersive_mode_summary">Oculta todas as barras do sistema em modo de tela cheia</string>
<string name="msg_ptr_hiding_not_supported">Ocultar cursor não é compatível com este dispositivo</string>
<string name="msg_ptr_hiding_not_supported">Não suportado neste dispositivo</string>
<string name="desc_discovery_stop_btn">Parar a descoberta de servidores</string>
<string name="pref_direct_touch_summary_off">Faça ações no ponteiro</string>
<string name="pref_mouse_back_action_default">Padrão</string>
<string name="pref_mouse_back_action_right">Clique com botão direito</string>
<string name="pref_mouse_back">Botão Voltar</string>
<string name="pref_mouse_back_action_middle">Clique com botão do meio</string>
<string name="pref_discovery_autorun">Execução automática</string>
<string name="pref_discovery_autorun_summary">Descubra servidores enquanto estiver na página inicial</string>
<string name="pref_rediscovery_indicator">Destaque servidores redescobertos</string>
<string name="pref_direct_touch_summary_on">Faça ações no ponto de contato</string>
<string name="pref_direct_touch">Toque direto</string>
<string name="title_default_security">Segurança padrão</string>
<string name="msg_drag_gesture_help">Ao atribuir ação de gesto de Arrastar você mudará Detecção de toque longo:
\n
\n<b>Toque-segure-solte</b> → Toque longo
\n<b>Toque-segure-deslize</b> → Arrastar</string>
<string name="pref_swipe_sensitivity">Sensibilidade de deslize</string>
<string name="pref_orientation">Orientação da tela</string>
<string name="pref_orientation_option_auto">Automática</string>
<string name="pref_orientation_option_portrait">Vertical</string>
<string name="pref_orientation_option_landscape">Horizontal</string>
</resources>

View file

@ -1,2 +1,146 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>
<resources>
<string name="title_add_server_profile">Добавить сервер</string>
<string name="title_edit_server_profile">Изменить сервер</string>
<string name="title_duplicate">Создать копию</string>
<string name="title_edit">Изменить</string>
<string name="title_delete">Удалить</string>
<string name="title_cancel">Отменить</string>
<string name="title_save">Сохранить</string>
<string name="title_settings">Настройки</string>
<string name="title_credentials_required">Войти</string>
<string name="title_export">Экспорт</string>
<string name="title_import">Импорт</string>
<string name="title_view_only_mode">Только просмотр</string>
<string name="msg_invalid_key_file">недействительный файл приватного ключа</string>
<string name="pref_servers">Серверы</string>
<string name="pref_mouse">Мышь</string>
<string name="title_default_security">Безопасность по-умолчанию</string>
<string name="title_delete_servers_before_import">Удалите текущие серверы перед импортом</string>
<string name="title_password">Пароль</string>
<string name="title_change">Изменить</string>
<string name="title_about">О программе</string>
<string name="title_license">Лицензия</string>
<string name="title_open_source_libraries">Библиотеки с открытым исходным кодом</string>
<string name="title_github_repo">Исходный код</string>
<string name="hint_urlbar">Подключится к серверу</string>
<string name="hint_server_name">Имя (например: Рабочий ПК)</string>
<string name="hint_host">Хост</string>
<string name="hint_port">Порт</string>
<string name="hint_server_id">Идентификатор сервера</string>
<string name="msg_exported">Экспортировано</string>
<string name="msg_export_auth_missing">Ваше устройство не защищено. Включите блокировку экрана для обеспечения возможности аутентификации перед экспортом.</string>
<string name="msg_copied_to_clipboard">Скопировано</string>
<string name="desc_saved_servers_tab">Сохранённые серверы</string>
<string name="desc_discovered_servers_tab">Найденные серверы</string>
<string name="desc_discovery_btn">Найти серверы</string>
<string name="desc_discovery_running">Поиск серверов в вашей сети</string>
<string name="desc_virtual_keys_btn">Показать виртуальные клавиши</string>
<string name="desc_reset_zoom_btn">Сбросить масштабирование</string>
<string name="desc_back_btn">Назад</string>
<string name="desc_help_btn">Помощь</string>
<string name="desc_discovered_list">Список автоматически найденных серверов</string>
<string name="pref_ui_summary">Внешний вид</string>
<string name="desc_retry_connection_btn">Повторить</string>
<string name="pref_input">Ввод</string>
<string name="pref_input_summary">Жесты, мышь, виртуальные клавиши</string>
<string name="pref_server_summary">Вход, обнаружение</string>
<string name="pref_tools">Инструменты</string>
<string name="pref_tools_summary">Импорт/Экспорт, отладка</string>
<string name="pref_gesture_action_left_click">Левая кнопка мыши</string>
<string name="pref_gesture_action_middle_click">Средняя кнопка мыши</string>
<string name="pref_gesture_action_remote_scroll">Прокручивать удалённое содержимое</string>
<string name="pref_theme_option_system">Система</string>
<string name="pref_gesture">Жесты</string>
<string name="pref_double_tap">Двойное нажатие</string>
<string name="pref_sort_server_list">Сортировать сохранённые серверы по имени</string>
<string name="pref_single_tap">Одиночное нажатие</string>
<string name="pref_discovery">Обнаружение</string>
<string name="pref_discovery_autorun">Автозапуск</string>
<string name="pref_experimental_toolbar_swipe_close_summary">Во время удалённой сессии, вы можете сделать свайп для закрытия открытой панели инструментов</string>
<string name="pref_gesture_action_none">Нет</string>
<string name="pref_gesture_action_right_click">Правая кнопка мыши</string>
<string name="pref_gesture_action_double_click">Двойное нажатие</string>
<string name="pref_vk_show_all">Показать все клавиши</string>
<string name="pref_vk_open_with_keyboard">Открыть с клавиатурой</string>
<string name="pref_km_language_switch_to_super">Переключение раскладки → Super</string>
<string name="pref_toolbar_alignment_option_start">Начало</string>
<string name="pref_toolbar">Панель инструментов</string>
<string name="pref_toolbar_alignment_option_end">Конец</string>
<string name="pref_zoom">Масштабирование</string>
<string name="pref_import_export">Импортировать/Экспортировать серверы</string>
<string name="pref_debugging">Отладка</string>
<string name="title_report_bug">Сообщить об ошибке</string>
<string name="msg_pip_not_supported">Не поддерживается на этом устройстве</string>
<string name="msg_ptr_hiding_not_supported">Не поддерживается на этом устройстве</string>
<string name="msg_loading">Загрузка…</string>
<string name="msg_imported">Импортировано</string>
<string name="tip_urlbar">Вы можете ввести IP адрес, имя хоста, или \'vnc://\' URI.</string>
<string name="desc_add_new_server_btn">Добавить сервер</string>
<string name="desc_save_discovered_server_btn">Сохранить</string>
<string name="desc_discovery_stop_btn">остановить поиск серверов</string>
<string name="desc_keyboard_btn">Открыть клавиатуру</string>
<string name="desc_clear_url_btn">Очистить</string>
<string name="desc_server_url">Адрес сервера</string>
<string name="pref_viewer">Просмотр</string>
<string name="pref_viewer_summary">Картинка-в-картинке, масштабирование</string>
<string name="pref_experimental">Экспериментальные</string>
<string name="pref_experimental_summary">Экспериментальные возможности</string>
<string name="pref_gesture_action_move_pointer">Движение курсора</string>
<string name="pref_gesture_action_remote_drag">Перетаскивать удалённое содержимое</string>
<string name="pref_gesture_action_open_keyboard">Открыть клавиатуру</string>
<string name="pref_theme_option_dark">Тёмная</string>
<string name="pref_theme_option_light">Светлая</string>
<string name="pref_prefer_advanced_editor">Напрямую открывать дополнительные настройки сервера</string>
<string name="pref_long_press">Длительное нажатие</string>
<string name="pref_swipe1">Свайп одним пальцем</string>
<string name="pref_swipe2">Свайп двумя пальцами</string>
<string name="pref_drag">Перетаскивание</string>
<string name="pref_swipe_sensitivity">Чувствительность свайпа</string>
<string name="pref_hide_local_cursor">Скрыть локальный курсор</string>
<string name="pref_mouse_back">Кнопка \"назад\"</string>
<string name="pref_mouse_back_action_default">По-умолчанию</string>
<string name="pref_mouse_back_action_right">Правая кнопка мыши</string>
<string name="pref_mouse_back_action_middle">Средняя кнопка мыши</string>
<string name="pref_vk">Виртуальные клавиши</string>
<string name="pref_km_right_alt_to_super">Правый Alt → Super</string>
<string name="pref_zoom_min">Минимум</string>
<string name="pref_cred_autocomplete">Авто-завершение</string>
<string name="pref_zoom_max">Максимум</string>
<string name="pref_orientation">Ориентация экрана</string>
<string name="pref_orientation_option_auto">Автоматически</string>
<string name="pref_orientation_option_portrait">Портретная</string>
<string name="title_clear">Очистить</string>
<string name="msg_server_profile_added">Сервер добавлен</string>
<string name="msg_server_profile_deleted">Сервер удалён</string>
<string name="pref_ui">Интерфейс</string>
<string name="title_copy_host">Скопировать хост</string>
<string name="title_use_ssh_tunnel">Использовать SSH туннель</string>
<string name="title_unknown_ssh_host">Неизвестный SSH хост</string>
<string name="title_ssh_host_key_changed">Ключ хоста SSH изменён</string>
<string name="msg_about_app">Быстрый и защищённый VNC клиент. Удалённо просматривайте и управляйте любым устройством на котором запущен VNC сервер.
\n
\n
\nAVNC это программное обеспечение с открытым исходным кодом - созданное при поддержке от большого количества участников.</string>
<string name="tip_empty_server_list">Список серверов пуст.
\nНажмите \'<b>+</b>\' для добавления сервера, или
\nиспользуйте верхнюю адресную строку для прямого подключения.</string>
<string name="pref_two_finger_tap">Нажатие двумя пальцами</string>
<string name="pref_enable_pip">Картинка-в-картинке</string>
<string name="title_copy_name">Скопировать имя</string>
<string name="pref_logs">Логи</string>
<string name="title_hide_remote_cursor">Скрыть удалённый курсор</string>
<string name="title_continue">Продолжить</string>
<string name="hint_username">Имя пользователя</string>
<string name="desc_settings_btn">Настройки</string>
<string name="desc_saved_servers_list">Список сохранённых серверов</string>
<string name="hint_password">Пароль</string>
<string name="pref_theme">Тема</string>
<string name="pref_km">Назначения клавиш</string>
<string name="pref_orientation_option_landscape">Ландшафтная</string>
<string name="pref_fullscreen">Полноэкранный режим</string>
<string name="pref_clipboard_sync">Синхронизация буфера обмена</string>
<string name="pref_credential">Вход</string>
<string name="pref_experimental_immersive_mode">Режим полного присутствия</string>
<string name="pref_experimental_immersive_mode_summary">Скрывает все системные панели в полноэкранном режиме</string>
</resources>

View file

@ -1,2 +1,165 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>
<resources>
<string name="hint_repeater_host">Понови домаћина</string>
<string name="hint_server_name">Име (нпр. Радни рачунар)</string>
<string name="msg_about_app">Брзи и безбедни корисник ВНЦ-а. Удаљени преглед и управљање било каквим уређајима који опслужују ВНЦ.
\n
\n
\nАВНЦ је отвореног извора и слободног (копилефт) умножавања — направљен уз помоћ многих доприносиоца.</string>
<string name="msg_invalid_vnc_uri">Није исправна адреса ВНЦ-а</string>
<string name="desc_reset_zoom_btn">Поврати увећање на подразумевано</string>
<string name="desc_add_new_server_btn">Додај служитеља</string>
<string name="desc_discovery_running">Откривам служитеље на овој мрежи</string>
<string name="pref_gesture_action_move_pointer">Померај показивач</string>
<string name="desc_saved_servers_list">Списак сачуваних служитеља</string>
<string name="desc_discovered_list">Списак самостално откривених служитеља</string>
<string name="pref_tools">Прибор</string>
<string name="pref_ui_summary">Изглед и дојам</string>
<string name="pref_viewer_summary">Слика у слици, увећање</string>
<string name="pref_servers">Служитељи</string>
<string name="pref_experimental">Опитно</string>
<string name="pref_server_summary">Пријава</string>
<string name="pref_tools_summary">Увоз/извоз, исправке грешака</string>
<string name="pref_experimental_summary">Додатна добра</string>
<string name="pref_gesture_action_none">Ништа</string>
<string name="pref_gesture_action_right_click">Десни клик</string>
<string name="pref_gesture_action_double_click">Двоклик</string>
<string name="pref_mouse_passthrough">Пролаз мишем</string>
<string name="pref_direct_touch">Непосредан додир</string>
<string name="pref_direct_touch_summary_off">Извршавај радње на показивачу</string>
<string name="pref_long_press">Дуги притисак</string>
<string name="pref_vk_open_with_keyboard">Отвори тастатуром</string>
<string name="pref_direct_touch_summary_on">Извршавај радње на тачци додира</string>
<string name="pref_mouse">Миш</string>
<string name="pref_theme">Тема</string>
<string name="pref_theme_option_light">Светла</string>
<string name="pref_prefer_advanced_editor">Отвори непосредно напредне могућности служитеља</string>
<string name="pref_two_finger_tap">Двопрсти тап</string>
<string name="pref_double_tap">Двоструки тап</string>
<string name="pref_swipe1">Прелаз једним прстом</string>
<string name="pref_vk">Тастатура заслона</string>
<string name="pref_drag">Превуци (дуги притисак и прелаз)</string>
<string name="pref_km">Распоред дугмади</string>
<string name="pref_fullscreen">Пуни приказ</string>
<string name="pref_hide_local_cursor">Сакриј местни (Андроидов) показивач</string>
<string name="pref_km_right_alt_to_super">Десни алт → супер</string>
<string name="pref_km_language_switch_to_super">Промена језика → супер</string>
<string name="pref_km_back_to_escape">Назад → бег</string>
<string name="pref_km_back_to_escape_summary">Не утиче на траку навођења</string>
<string name="pref_credential">Пријава</string>
<string name="desc_discovered_servers_tab">Откривени служитељи</string>
<string name="desc_save_discovered_server_btn">Сачувај</string>
<string name="desc_discovery_btn">Откриј служитеље</string>
<string name="desc_keyboard_btn">Отвори тастатуру</string>
<string name="desc_virtual_keys_btn">Прикажи тастатуру заслона</string>
<string name="desc_retry_connection_btn">Поново покушај</string>
<string name="desc_back_btn">Назад</string>
<string name="desc_clear_url_btn">Очисти</string>
<string name="desc_server_url">Адреса служитеља</string>
<string name="desc_help_btn">Помоћ</string>
<string name="pref_ui">Сучеље</string>
<string name="pref_viewer">Прегледач</string>
<string name="pref_input">Унос</string>
<string name="pref_input_summary">Потези, миш, дугмад заслона</string>
<string name="pref_gesture_action_left_click">Леви клик</string>
<string name="pref_gesture_action_middle_click">Средњи клик</string>
<string name="pref_gesture_action_remote_scroll">Премичи удаљени садржај</string>
<string name="pref_gesture_action_remote_drag">Вуци удаљени садржај</string>
<string name="pref_gesture_action_open_keyboard">Отвори тастатуру</string>
<string name="pref_theme_option_system">Систем</string>
<string name="pref_theme_option_dark">Тамна</string>
<string name="pref_sort_server_list">Распореди сачуване служитеље по имену</string>
<string name="pref_gesture">Потези</string>
<string name="pref_single_tap">Један тап</string>
<string name="pref_swipe2">Прелаз двома прстима</string>
<string name="pref_mouse_passthrough_summary_on">Пошаљи догађаје миша непосредно служитељу</string>
<string name="pref_toolbar_alignment_option_end">Крај</string>
<string name="pref_zoom">Увећање</string>
<string name="pref_zoom_min">Најмање</string>
<string name="pref_zoom_max">Највише</string>
<string name="pref_clipboard_sync">Услкађивање складишта исечака</string>
<string name="pref_cred_autocomplete">Самопопуна</string>
<string name="pref_cred_autocomplete_summary">Предлаже сачуване пријаве при повезивању са новим служитељем</string>
<string name="pref_discovery">Откривање</string>
<string name="pref_import_export">Увези/извези служитеље</string>
<string name="pref_debugging">Отклон грешака</string>
<string name="pref_logs">Дневници</string>
<string name="title_add_server_profile">Додај служитеља</string>
<string name="title_edit_server_profile">Уреди служитеља</string>
<string name="title_duplicate">Удвостручи</string>
<string name="title_edit">Уреди</string>
<string name="title_delete">Избриши</string>
<string name="title_clear">Очисти</string>
<string name="title_copy_host">Умножи домаћина</string>
<string name="title_copy_name">Умножи име</string>
<string name="title_advanced">Напредно</string>
<string name="title_cancel">Откажи</string>
<string name="title_save">Сачувај</string>
<string name="title_undo">Опозови</string>
<string name="title_settings">Поставке</string>
<string name="title_credentials_required">Пријава</string>
<string name="title_remember">Запамти</string>
<string name="title_export">Извези</string>
<string name="title_import">Увези</string>
<string name="title_view_only_mode">Само за читање</string>
<string name="title_hide_remote_cursor">Сакриј удаљеног показивача</string>
<string name="title_key_compat_mode">Пошаљи старинске догађаје дугмади</string>
<string name="title_unknown_ssh_host">Непознати домаћин ССХ-а</string>
<string name="title_use_ssh_tunnel">Користи тунел ССХ-а</string>
<string name="title_ssh_host_key_changed">Кључ домаћина ССХ-а је промењен</string>
<string name="title_continue">Настави</string>
<string name="title_delete_servers_before_import">Избриши тренутне служитеље пре увоза</string>
<string name="title_password">Лозинка</string>
<string name="title_use_repeater">Користи понављање</string>
<string name="title_private_key">Лични кључ</string>
<string name="title_change">Измени</string>
<string name="title_about">О програму</string>
<string name="title_license">Дозвола</string>
<string name="title_open_source_libraries">Књижнице отвореног извора</string>
<string name="title_report_bug">Пријави проблем</string>
<string name="title_github_repo">Изворни код</string>
<string name="hint_urlbar">Повежи се са служитељем</string>
<string name="hint_host">Домаћин</string>
<string name="hint_port">Прикључник</string>
<string name="hint_server_id">Назив служитеља</string>
<string name="hint_username">Корисничко име</string>
<string name="hint_password">Лозинка</string>
<string name="hint_key_password">Лозинка кључа</string>
<string name="msg_server_profile_added">Додат је служитељ</string>
<string name="msg_server_profile_deleted">Избрисан је служитељ</string>
<string name="msg_pip_not_supported">Нема подршке на овом уређају</string>
<string name="msg_ptr_hiding_not_supported">Није подржано на овом уређају</string>
<string name="msg_loading">Учитавам…</string>
<string name="msg_imported">Увезено</string>
<string name="msg_exported">Извезено</string>
<string name="msg_export_auth_required">Пријавите се ради извоза служитеља</string>
<string name="msg_export_auth_missing">Ваш уређај није обезбеђен. Укључите закључавање заслона ради омогућавања пријаве пре извоза.</string>
<string name="msg_invalid_key_file">Датотека личног кључа није исправна</string>
<string name="msg_copied_to_clipboard">Умножено</string>
<string name="msg_key_compat_mode_help">Неки служитељи имају ограничену подршку уникода. Застарели догађаји дугмади могу помоћи за неенглеско писмо.</string>
<string name="tip_empty_server_list">Списак служитеља је празан.
\nКликните на „<b>+</b>“ ради додавања служитеља, или
\nупотребите горњу траку уноса ради непосредног повезивања.</string>
<string name="tip_urlbar">Можете унети адресу ИП-а, име домаћина, или \'vnc://\' URI.</string>
<string name="tip_discovery">Откривање може самостално да пронађе служитеље
\nкоји раде на Вашој мрежи.</string>
<string name="desc_navigation_btn">Отвори крмило навођења</string>
<string name="desc_settings_btn">Поставке</string>
<string name="desc_saved_servers_tab">Сачувани служитељи</string>
<string name="pref_mouse_passthrough_summary_off">Користи догађаје миша за местне потезе</string>
<string name="pref_vk_show_all">Приказуј сву дугмад</string>
<string name="pref_enable_pip">Слика у слици</string>
<string name="pref_toolbar">Трака прибора</string>
<string name="pref_toolbar_alignment">Поравнање</string>
<string name="pref_toolbar_alignment_option_start">Почетак</string>
<string name="pref_gesture_action_pan">Премести положај оквира</string>
<string name="pref_keycode_map">Мапа распореда дугмади</string>
<string name="pref_experimental_toolbar_swipe_close">Превуци за затварање траке прибора</string>
<string name="pref_experimental_toolbar_swipe_close_summary">Током удаљене седнице можете превући ради затварања тренутно отворене траке прибора</string>
<string name="pref_experimental_immersive_mode">Потпуно утопљен приказ</string>
<string name="pref_experimental_immersive_mode_summary">Скрива све траке система у пуном приказу заслона</string>
<string name="pref_discovery_autorun_summary">Откривај служитеље од матичне стране</string>
<string name="desc_discovery_stop_btn">Престани да откриваш служитеље</string>
<string name="pref_discovery_autorun">Самопокрени</string>
<string name="pref_rediscovery_indicator">Истакни поново пронађене служитеље</string>
</resources>

View file

@ -0,0 +1,179 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="title_edit">Düzenle</string>
<string name="title_delete">Sil</string>
<string name="title_clear">Temizle</string>
<string name="title_copy_host">Ana makineyi kopyala</string>
<string name="title_copy_name">Adı kopyala</string>
<string name="title_advanced">Gelişmiş</string>
<string name="title_cancel">İptal</string>
<string name="title_undo">Geri al</string>
<string name="title_settings">Ayarlar</string>
<string name="title_credentials_required">Oturum aç</string>
<string name="title_remember">Hatırla</string>
<string name="title_export">Dışa aktar</string>
<string name="title_import">İçe aktar</string>
<string name="title_view_only_mode">Yalnızca görüntüle</string>
<string name="title_use_ssh_tunnel">SSH tüneli kullan</string>
<string name="title_unknown_ssh_host">Bilinmeyen SSH ana makinesi</string>
<string name="title_continue">Devam et</string>
<string name="title_use_repeater">Yineleyici kullan</string>
<string name="title_private_key">Özel anahtar</string>
<string name="title_change">Değiştir</string>
<string name="title_about">Hakkında</string>
<string name="title_license">Lisans</string>
<string name="title_open_source_libraries">Açık kaynaklı kütüphaneler</string>
<string name="hint_urlbar">Bir sunucuya bağlan</string>
<string name="hint_server_name">Ad (örn. İş Bilgisayarı)</string>
<string name="hint_host">Ana makine</string>
<string name="hint_repeater_host">Yineleyici ana makine</string>
<string name="hint_password">Parola</string>
<string name="msg_about_app">Hızlı ve güvenli bir VNC istemcisi. Bir VNC sunucusu çalıştıran herhangi bir aygıtı uzaktan görüntüleyin ve denetleyin.
\n
\n
\nAVNC, katkıda bulunan birçok kişinin yardımıyla yapılmış, açık kaynaklı ve copyleft bir yazılımdır.</string>
<string name="msg_server_profile_added">Sunucu eklendi</string>
<string name="msg_server_profile_deleted">Sunucu silindi</string>
<string name="msg_pip_not_supported">Bu aygıtta desteklenmiyor</string>
<string name="msg_invalid_key_file">Geçersiz özel anahtar dosyası</string>
<string name="msg_invalid_vnc_uri">Geçersiz VNC URI\'si</string>
<string name="tip_urlbar">IP adresi, ana makine adı veya \'vnc://\' URI\'si girebilirsiniz.</string>
<string name="desc_navigation_btn">Gezinme çekmecesini aç</string>
<string name="desc_discovered_servers_tab">Keşfedilen sunucular</string>
<string name="desc_discovery_stop_btn">Sunucu keşfetmeyi durdur</string>
<string name="desc_discovery_running">Ağınızdaki sunucular keşfediliyor</string>
<string name="desc_keyboard_btn">Klavyeyi aç</string>
<string name="pref_ui">Arayüz</string>
<string name="pref_ui_summary">Görünüm</string>
<string name="pref_viewer">Görüntüleyici</string>
<string name="pref_viewer_summary">Resim içinde resim, yakınlaştırma</string>
<string name="pref_input">Giriş</string>
<string name="pref_input_summary">Hareketler, fare, sanal tuşlar</string>
<string name="pref_servers">Sunucular</string>
<string name="pref_experimental_summary">Ek güzellikler</string>
<string name="pref_gesture_action_none">Hiçbiri</string>
<string name="pref_gesture_action_left_click">Sol tıklama</string>
<string name="pref_gesture_action_double_click">Çift tıklama</string>
<string name="pref_gesture_action_move_pointer">İşaretçiyi hareket ettir</string>
<string name="pref_gesture_action_pan">Yerel çerçeveyi kaydır</string>
<string name="pref_gesture_action_open_keyboard">Klavyeyi aç</string>
<string name="pref_theme_option_dark">Koyu</string>
<string name="pref_direct_touch_summary_off">Eylemleri işaretçinin olduğu yerde yap</string>
<string name="pref_single_tap">Tek dokunma</string>
<string name="pref_swipe1">Tek parmakla kaydırma</string>
<string name="pref_drag">Sürükleme</string>
<string name="pref_mouse">Fare</string>
<string name="pref_mouse_passthrough_summary_off">Yerel hareketler için fare olaylarını kullan</string>
<string name="pref_km">Tuş eşlemeleri</string>
<string name="pref_km_right_alt_to_super">Sağ Alt → Süper</string>
<string name="pref_km_back_to_escape">Geri → Escape</string>
<string name="pref_km_back_to_escape_summary">Gezinme çubuğunu etkilemez</string>
<string name="pref_fullscreen">Tam ekran</string>
<string name="pref_enable_pip">Resim içinde resim</string>
<string name="pref_toolbar">Araç çubuğu</string>
<string name="pref_toolbar_alignment_option_end">Bitiş</string>
<string name="pref_zoom">Yakınlaştır</string>
<string name="pref_zoom_min">En az</string>
<string name="pref_zoom_max">En fazla</string>
<string name="pref_credential">Oturum aç</string>
<string name="pref_cred_autocomplete">Otomatik tamamla</string>
<string name="pref_rediscovery_indicator">Yeniden keşfedilen sunucuları vurgula</string>
<string name="pref_import_export">Sunucuları içe/dışa aktar</string>
<string name="pref_debugging">Hata ayıklama</string>
<string name="pref_logs">Günlük kayıtları</string>
<string name="pref_keycode_map">Tuş kodu eşlemesi</string>
<string name="pref_experimental_toolbar_swipe_close">Araç çubuğu çekmecesini kapatmak için kaydır</string>
<string name="pref_experimental_immersive_mode">Tam odaklanma modu</string>
<string name="pref_experimental_immersive_mode_summary">Tam ekran modunda tüm sistem çubuklarını gizler</string>
<string name="title_save">Kaydet</string>
<string name="title_key_compat_mode">Eski tuş olaylarını gönder</string>
<string name="title_hide_remote_cursor">Uzak işaretçiyi gizle</string>
<string name="title_delete_servers_before_import">İçe aktarmadan önce geçerli sunucuları sil</string>
<string name="desc_clear_url_btn">Temizle</string>
<string name="title_add_server_profile">Sunucu ekle</string>
<string name="title_github_repo">Kaynak kodu</string>
<string name="title_edit_server_profile">Sunucuyu düzenle</string>
<string name="title_ssh_host_key_changed">SSH ana makine anahtarı değişti</string>
<string name="title_password">Parola</string>
<string name="title_report_bug">Sorun bildir</string>
<string name="title_duplicate">Çoğalt</string>
<string name="msg_ptr_hiding_not_supported">Bu aygıtta desteklenmiyor</string>
<string name="hint_port">Bağlantı noktası</string>
<string name="tip_empty_server_list">Sunucu listesi boş.
\nBir sunucu eklemek için \'<b>+</b>\' simgesine tıklayın veya
\ndoğrudan bağlanmak için üst adres çubuğunu kullanın.</string>
<string name="pref_vk_show_all">Tüm tuşları göster</string>
<string name="hint_server_id">Sunucu kimliği</string>
<string name="hint_username">Kullanıcı adı</string>
<string name="msg_loading">Yükleniyor…</string>
<string name="msg_imported">İçe aktarıldı</string>
<string name="hint_key_password">Anahtar parolası</string>
<string name="msg_exported">Dışa aktarıldı</string>
<string name="msg_export_auth_missing">Aygıtınız güvenli değil. Dışa aktarmadan önce kimlik doğrulamasını etkinleştirmek için ekran kilidini etkinleştirin.</string>
<string name="msg_copied_to_clipboard">Kopyalandı</string>
<string name="msg_key_compat_mode_help">Bazı sunucular sınırlı Unicode desteğine sahiptir. Eski tuş olayları, İngilizce olmayan metinlerin girilmesine yardımcı olabilir.</string>
<string name="msg_export_auth_required">Sunucuları dışa aktarmak için kimliğinizi doğrulayın</string>
<string name="pref_tools_summary">İçe/dışa aktarma, hata ayıklama</string>
<string name="pref_theme">Tema</string>
<string name="tip_discovery">Keşif, ağınızda çalışan desteklenen sunucuları otomatik
\nolarak bulabilir.</string>
<string name="desc_settings_btn">Ayarlar</string>
<string name="desc_add_new_server_btn">Sunucu ekle</string>
<string name="desc_discovery_btn">Sunucu keşfet</string>
<string name="desc_retry_connection_btn">Yeniden dene</string>
<string name="desc_back_btn">Geri</string>
<string name="pref_experimental">Deneysel</string>
<string name="desc_saved_servers_tab">Kaydedilen sunucular</string>
<string name="desc_save_discovered_server_btn">Kaydet</string>
<string name="desc_reset_zoom_btn">Yakınlaştırmayı sıfırla</string>
<string name="pref_server_summary">Oturum açma, keşif</string>
<string name="pref_tools">Araçlar</string>
<string name="pref_gesture_action_middle_click">Orta tıklama</string>
<string name="desc_virtual_keys_btn">Sanal tuşları göster</string>
<string name="desc_server_url">Sunucu adresi</string>
<string name="desc_help_btn">Yardım</string>
<string name="desc_discovered_list">Otomatik olarak keşfedilen sunucuların listesi</string>
<string name="desc_saved_servers_list">Kaydedilen sunucuların listesi</string>
<string name="pref_gesture_action_right_click">Sağ tıklama</string>
<string name="pref_direct_touch">Doğrudan dokunma</string>
<string name="pref_toolbar_alignment_option_start">Başlangıç</string>
<string name="pref_discovery">Keşif</string>
<string name="pref_discovery_autorun_summary">Ana sayfadayken sunucuları keşfet</string>
<string name="pref_experimental_toolbar_swipe_close_summary">Uzak bir oturum sırasında, o anda açık olan araç çubuğunu kapatmak için kaydırabilirsiniz</string>
<string name="pref_theme_option_system">Sistem</string>
<string name="pref_gesture_action_remote_scroll">Uzak içeriği kaydır</string>
<string name="pref_gesture_action_remote_drag">Uzak içeriği sürükle</string>
<string name="pref_theme_option_light">Açık</string>
<string name="pref_prefer_advanced_editor">Doğrudan gelişmiş sunucu seçeneklerini aç</string>
<string name="pref_sort_server_list">Kaydedilen sunucuları ada göre sırala</string>
<string name="pref_gesture">Hareketler</string>
<string name="pref_direct_touch_summary_on">Eylemleri dokunma noktasında yap</string>
<string name="pref_vk">Sanal tuşlar</string>
<string name="pref_two_finger_tap">İki parmakla dokunma</string>
<string name="pref_double_tap">Çift dokunma</string>
<string name="pref_long_press">Uzun basma</string>
<string name="pref_swipe2">İki parmakla kaydırma</string>
<string name="pref_mouse_passthrough_summary_on">Fare olaylarını doğrudan sunucuya gönder</string>
<string name="pref_hide_local_cursor">Yerel işaretçiyi gizle</string>
<string name="pref_km_language_switch_to_super">Dil Değiştirme → Süper</string>
<string name="pref_toolbar_alignment">Hizalama</string>
<string name="pref_clipboard_sync">Panoyu eşzamanla</string>
<string name="pref_cred_autocomplete_summary">Yeni sunuculara bağlanırken kayıtlı oturum açma bilgilerini öner</string>
<string name="pref_discovery_autorun">Otomatik çalıştır</string>
<string name="pref_mouse_passthrough">Fare geçişi</string>
<string name="pref_vk_open_with_keyboard">Klavye ile aç</string>
<string name="pref_mouse_back_action_right">Sağ tıklama</string>
<string name="pref_mouse_back_action_middle">Orta tıklama</string>
<string name="pref_mouse_back">Geri düğmesi</string>
<string name="pref_mouse_back_action_default">Öntanımlı</string>
<string name="pref_swipe_sensitivity">Kaydırma hassasiyeti</string>
<string name="title_default_security">Öntanımlı güvenlik</string>
<string name="msg_drag_gesture_help">Sürükleme hareketine bir eylem atamak, Uzun basma algılamasını değiştirecektir:
\n
\n<b>Basılı tut ve bırak</b> → Uzun bas
\n<b>Basılı tut ve kaydır</b> → Sürükle</string>
<string name="pref_orientation_option_portrait">Dikey</string>
<string name="pref_orientation_option_landscape">Yatay</string>
<string name="pref_orientation">Ekran yönü</string>
<string name="pref_orientation_option_auto">Otomatik</string>
</resources>

View file

@ -0,0 +1,179 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pref_gesture_action_remote_drag">Перетягнути віддалений вміст</string>
<string name="pref_gesture_action_open_keyboard">Відкрити клавіатуру</string>
<string name="pref_theme_option_system">Системна</string>
<string name="pref_theme_option_dark">Темна</string>
<string name="pref_theme_option_light">Світла</string>
<string name="pref_sort_server_list">Сортувати збережені сервери за назвою</string>
<string name="pref_gesture">Жести</string>
<string name="pref_prefer_advanced_editor">Відразу відкривати розширені параметри сервера</string>
<string name="pref_direct_touch_summary_off">Робити дії з вказівником</string>
<string name="pref_direct_touch">Дотик напряму</string>
<string name="pref_single_tap">Один дотик</string>
<string name="pref_swipe1">Ковзання одним пальцем</string>
<string name="pref_mouse">Мишка</string>
<string name="pref_fullscreen">На повний екран</string>
<string name="pref_toolbar">Панель засобів</string>
<string name="pref_zoom">Збільшення</string>
<string name="pref_discovery">Виявлення</string>
<string name="pref_debugging">Зневадження</string>
<string name="pref_logs">Журнал</string>
<string name="title_add_server_profile">Додати сервер</string>
<string name="title_edit">Змінити</string>
<string name="title_advanced">Розширено</string>
<string name="title_use_ssh_tunnel">Викор. тунель SSH</string>
<string name="hint_password">Пароль</string>
<string name="title_delete_servers_before_import">Видалити поточні сервери перед імпортуванням</string>
<string name="title_open_source_libraries">Бібліотеки з відкритим кодом</string>
<string name="title_license">Ліцензія</string>
<string name="hint_urlbar">Підʼєднатися до сервера</string>
<string name="hint_server_id">ІД сервера</string>
<string name="msg_pip_not_supported">Не підтримується на цьому пристрої</string>
<string name="msg_about_app">Швидкий і безпечний VNC-клієнт. Переглядайте і керуйте віддалено будь-яким пристроєм, що працює на VNC-сервері.
\n
\n
\nAVNC це copyleft-ПЗ з відкритим кодом — зроблене з допомогою багатьох співрозробників.</string>
<string name="msg_invalid_key_file">Неправильний файл приватного ключа</string>
<string name="msg_invalid_vnc_uri">Неправильна URI-адреса VNC</string>
<string name="desc_save_discovered_server_btn">Зберегти</string>
<string name="pref_input_summary">Жести, мишка, віртуальні клавіші</string>
<string name="pref_km_right_alt_to_super">Правий Alt → Super</string>
<string name="pref_toolbar_alignment">Вирівнювання</string>
<string name="tip_empty_server_list">Список серверів порожній.
\nНатисніть \'<b>+</b>\', щоб додати сервер, або
\nвикористовуйте верхній рядок адреси, щоб зʼєднатися напряму.</string>
<string name="desc_navigation_btn">Відкрити шторку переходів</string>
<string name="desc_clear_url_btn">Очистити</string>
<string name="pref_ui">Інтерфейс</string>
<string name="pref_viewer_summary">Картинка-в-картинці, збільшення</string>
<string name="desc_saved_servers_tab">Збережені сервери</string>
<string name="desc_add_new_server_btn">Додати сервер</string>
<string name="desc_keyboard_btn">Відкрити клавіатуру</string>
<string name="desc_server_url">Адреса сервера</string>
<string name="desc_help_btn">Довідка</string>
<string name="pref_viewer">Переглядач</string>
<string name="pref_tools">Засоби</string>
<string name="pref_gesture_action_double_click">Подвійне клацання</string>
<string name="pref_gesture_action_pan">Посунути локальний фрейм</string>
<string name="pref_experimental_summary">Додаткові можливості</string>
<string name="pref_gesture_action_left_click">Ліве клацання</string>
<string name="pref_gesture_action_move_pointer">Перемістити вказівник</string>
<string name="pref_clipboard_sync">Синхронізувати буфери обміну</string>
<string name="pref_gesture_action_remote_scroll">Прокрутити віддалений вміст</string>
<string name="pref_km_language_switch_to_super">Перемикання мови → Super</string>
<string name="pref_toolbar_alignment_option_start">Початок</string>
<string name="pref_theme">Тема</string>
<string name="pref_long_press">Довге натискання</string>
<string name="pref_swipe2">Ковзання двома пальцями</string>
<string name="pref_drag">Перетягування</string>
<string name="pref_hide_local_cursor">Ховати локальний вказівник</string>
<string name="pref_vk">Віртуальні клавіші</string>
<string name="pref_vk_show_all">Показувати всі клавіші</string>
<string name="pref_km_back_to_escape">Назад → Escape</string>
<string name="pref_km_back_to_escape_summary">Не впливає на панель пересування</string>
<string name="pref_zoom_max">Найбільше</string>
<string name="pref_cred_autocomplete">Автодоповнення</string>
<string name="pref_enable_pip">Картинка-в-картинці</string>
<string name="pref_toolbar_alignment_option_end">Кінець</string>
<string name="pref_zoom_min">Найменше</string>
<string name="pref_credential">Вхід</string>
<string name="pref_cred_autocomplete_summary">Пропонувати збережені дані входу під час зʼєднання з новими серверами</string>
<string name="pref_experimental_immersive_mode_summary">Ховає всі системні панелі в повноекранному режимі</string>
<string name="pref_import_export">Імпорт/експорт серверів</string>
<string name="pref_keycode_map">Мапа кодів клавіш</string>
<string name="pref_experimental_toolbar_swipe_close">Ковзнути, щоб закрити шторку панелі засобів</string>
<string name="title_delete">Видалити</string>
<string name="title_edit_server_profile">Змінити сервер</string>
<string name="title_duplicate">Дублювати</string>
<string name="title_clear">Очистити</string>
<string name="title_copy_host">Копіювати хост</string>
<string name="title_copy_name">Копіювати назву</string>
<string name="title_cancel">Скасувати</string>
<string name="title_save">Зберегти</string>
<string name="title_settings">Налаштування</string>
<string name="title_export">Експортувати</string>
<string name="title_import">Імпортувати</string>
<string name="title_view_only_mode">Лише перегляд</string>
<string name="title_hide_remote_cursor">Ховати віддалений вказівник</string>
<string name="title_unknown_ssh_host">Невідомий SSH-хост</string>
<string name="title_ssh_host_key_changed">Ключ SSH-хоста змінено</string>
<string name="title_continue">Продовжити</string>
<string name="title_password">Пароль</string>
<string name="title_use_repeater">Викор. повторювач</string>
<string name="title_private_key">Приватний ключ</string>
<string name="title_change">Змінити</string>
<string name="title_about">Про застосунок</string>
<string name="title_undo">Відмінити</string>
<string name="title_credentials_required">Вхід</string>
<string name="title_key_compat_mode">Надсилати успадковані події клавіш</string>
<string name="title_report_bug">Звіт про ваду</string>
<string name="title_github_repo">Вихідний код</string>
<string name="hint_server_name">Назва (напр., Робочий ПК)</string>
<string name="hint_host">Хост</string>
<string name="hint_repeater_host">Хост повторювача</string>
<string name="hint_port">Порт</string>
<string name="hint_username">Імʼя користувача</string>
<string name="msg_server_profile_added">Сервер додано</string>
<string name="hint_key_password">Пароль ключа</string>
<string name="msg_server_profile_deleted">Сервер видалено</string>
<string name="msg_ptr_hiding_not_supported">Не підтримується на цьому пристрої</string>
<string name="msg_loading">Завантаження…</string>
<string name="msg_imported">Імпортовано</string>
<string name="msg_exported">Експортовано</string>
<string name="msg_export_auth_required">Автентифікування для експортування серверів</string>
<string name="msg_copied_to_clipboard">Скопійовано</string>
<string name="tip_urlbar">Ви можете ввести IP-адресу, назву хоста або URI-адресу \'vnc://\'.</string>
<string name="desc_settings_btn">Налаштування</string>
<string name="desc_discovered_servers_tab">Виявлені сервери</string>
<string name="desc_discovery_btn">Виявлення серверів</string>
<string name="desc_discovery_running">Виявлення серверів у Вашій мережі</string>
<string name="desc_virtual_keys_btn">Показати віртуальні клавіші</string>
<string name="desc_back_btn">Назад</string>
<string name="desc_reset_zoom_btn">Скинути збільшення</string>
<string name="desc_discovered_list">Перелік автоматично виявлених серверів</string>
<string name="desc_saved_servers_list">Перелік збережених серверів</string>
<string name="pref_ui_summary">Смак і дотик</string>
<string name="pref_input">Введення</string>
<string name="pref_servers">Сервери</string>
<string name="pref_server_summary">Вхід, виявлення</string>
<string name="pref_tools_summary">Імпортування/експортування, зневадження</string>
<string name="pref_experimental">Експериментальне</string>
<string name="pref_gesture_action_none">Нічого</string>
<string name="pref_gesture_action_middle_click">Середнє клацання</string>
<string name="pref_gesture_action_right_click">Праве клацання</string>
<string name="pref_two_finger_tap">Торкання двома пальцями</string>
<string name="title_remember">Памʼятати</string>
<string name="msg_export_auth_missing">Ваш пристрій не захищено. Увімкніть блокування екрана, щоб дозволити автентифікування перед експортуванням.</string>
<string name="msg_key_compat_mode_help">Деякі сервери мають обмежену підтримку Unicode. Успадковані події клавіш допоможуть вводити не-англійський текст.</string>
<string name="desc_retry_connection_btn">Повторити</string>
<string name="tip_discovery">Виявлення може автоматично знаходити підтримувані сервери,
\nщо працюють у Вашій мережі.</string>
<string name="pref_direct_touch_summary_on">Робити дії в точці доторкання</string>
<string name="pref_double_tap">Подвійний дотик</string>
<string name="pref_mouse_passthrough">Перехід миші</string>
<string name="pref_mouse_passthrough_summary_off">Використовувати події миші для локальних жестів</string>
<string name="pref_experimental_toolbar_swipe_close_summary">Під час віддаленого сеансу Ви можете ковзнути, щоб закрити відкриту панель засобів</string>
<string name="pref_mouse_passthrough_summary_on">Надсилати події миші відразу на сервер</string>
<string name="pref_vk_open_with_keyboard">Відкривати з клавіатурою</string>
<string name="pref_km">Мапування клавіш</string>
<string name="pref_experimental_immersive_mode">Повний імерсивний режим</string>
<string name="pref_rediscovery_indicator">Виділяти попередньо виявлені сервери</string>
<string name="desc_discovery_stop_btn">Зупинити виявлення серверів</string>
<string name="pref_discovery_autorun">Автозапуск</string>
<string name="pref_discovery_autorun_summary">Виявляти сервери поки на домашній сторінці</string>
<string name="pref_mouse_back">Кнопка назад</string>
<string name="pref_mouse_back_action_default">Типово</string>
<string name="pref_mouse_back_action_right">Клацання правою кнопкою</string>
<string name="pref_mouse_back_action_middle">Клацання середньою кнопкою</string>
<string name="pref_swipe_sensitivity">Чутливість ковзання</string>
<string name="title_default_security">Типовий захист</string>
<string name="msg_drag_gesture_help">Присвоєння дії жесту Перетягування змінить виявлення довгого натискання:
\n
\n<b>Натиснути-утримувати-відпустити</b> → Довге натискання
\n<b>Натиснути-утримувати-потягнути</b> → Перетягування</string>
<string name="pref_orientation">Орієнтація екрана</string>
<string name="pref_orientation_option_auto">Автоматична</string>
<string name="pref_orientation_option_portrait">Портретна</string>
<string name="pref_orientation_option_landscape">Альбомна</string>
</resources>

View file

@ -0,0 +1,179 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="title_add_server_profile">添加服务器</string>
<string name="title_hide_remote_cursor">隐藏远程指针</string>
<string name="title_credentials_required">登录</string>
<string name="title_edit_server_profile">编辑服务器</string>
<string name="title_duplicate">复本</string>
<string name="title_edit">编辑</string>
<string name="title_delete">删除</string>
<string name="title_copy_host">复制主机</string>
<string name="title_copy_name">复制名称</string>
<string name="title_cancel">取消</string>
<string name="title_save">保存</string>
<string name="title_undo">撤消</string>
<string name="title_remember">记忆</string>
<string name="title_import">导入</string>
<string name="title_view_only_mode">仅查看</string>
<string name="title_use_ssh_tunnel">使用 SSH 隧道</string>
<string name="title_ssh_host_key_changed">SSH 主机密钥已更改</string>
<string name="title_password">密码</string>
<string name="title_private_key">私钥</string>
<string name="title_about">关于</string>
<string name="title_license">许可证</string>
<string name="hint_urlbar">连接到服务器</string>
<string name="hint_server_name">名称(例如:Work PC)</string>
<string name="hint_host">主机</string>
<string name="hint_repeater_host">中继器主机</string>
<string name="hint_port">端口</string>
<string name="hint_server_id">服务器 ID</string>
<string name="hint_username">用户名</string>
<string name="hint_password">密码</string>
<string name="hint_key_password">密钥密码</string>
<string name="msg_server_profile_added">服务器已添加</string>
<string name="msg_server_profile_deleted">服务器已删除</string>
<string name="msg_ptr_hiding_not_supported">此设备不支持</string>
<string name="msg_imported">已导入</string>
<string name="msg_exported">已导出</string>
<string name="msg_export_auth_required">身份验证以导出服务器</string>
<string name="msg_invalid_key_file">私钥文件无效</string>
<string name="msg_invalid_vnc_uri">无效的 VNC URI</string>
<string name="msg_copied_to_clipboard">已复制</string>
<string name="tip_urlbar">你可以输入 IP 地址、主机名或“vnc://”URI。</string>
<string name="desc_navigation_btn">打开导航抽屉</string>
<string name="desc_settings_btn">设置</string>
<string name="desc_saved_servers_tab">已保存的服务器</string>
<string name="desc_discovered_servers_tab">已发现的服务器</string>
<string name="desc_add_new_server_btn">添加服务器</string>
<string name="desc_save_discovered_server_btn">保存</string>
<string name="desc_discovery_btn">发现服务器</string>
<string name="desc_discovery_stop_btn">停止发现服务器</string>
<string name="desc_discovery_running">发现网络上的服务器</string>
<string name="desc_keyboard_btn">打开键盘</string>
<string name="desc_virtual_keys_btn">显示虚拟按键</string>
<string name="desc_reset_zoom_btn">重置缩放</string>
<string name="desc_retry_connection_btn">重试</string>
<string name="desc_back_btn">返回</string>
<string name="desc_clear_url_btn">清除</string>
<string name="desc_server_url">服务器地址</string>
<string name="desc_help_btn">帮助</string>
<string name="tip_discovery">发现 可以自动查找在网络上运行的
\n受支持服务器。</string>
<string name="pref_ui">界面</string>
<string name="pref_ui_summary">界面外观</string>
<string name="pref_viewer">视图</string>
<string name="pref_servers">服务器</string>
<string name="pref_server_summary">登录、发现</string>
<string name="pref_tools_summary">导入/导出、调试</string>
<string name="pref_experimental">试验性</string>
<string name="pref_gesture_action_none">无</string>
<string name="pref_gesture_action_left_click">左键单击</string>
<string name="pref_gesture_action_middle_click">中键单击</string>
<string name="pref_gesture_action_right_click">右键单击</string>
<string name="pref_gesture_action_double_click">双击</string>
<string name="pref_gesture_action_move_pointer">移动指针</string>
<string name="pref_gesture_action_remote_scroll">滚动远程内容</string>
<string name="pref_gesture_action_remote_drag">拖动远程内容</string>
<string name="pref_theme">主题</string>
<string name="pref_theme_option_system">系统</string>
<string name="pref_theme_option_dark">暗色</string>
<string name="pref_theme_option_light">亮色</string>
<string name="pref_gesture">手势</string>
<string name="pref_direct_touch">直接触摸</string>
<string name="pref_direct_touch_summary_on">在接触点执行操作</string>
<string name="pref_direct_touch_summary_off">在指针处执行操作</string>
<string name="pref_two_finger_tap">双指敲击</string>
<string name="pref_double_tap">双次敲击</string>
<string name="pref_long_press">长按</string>
<string name="pref_swipe1">单指轻扫</string>
<string name="pref_swipe2">双指轻扫</string>
<string name="pref_drag">拖动</string>
<string name="pref_mouse">鼠标</string>
<string name="pref_mouse_passthrough">鼠标直通</string>
<string name="pref_mouse_passthrough_summary_on">将鼠标事件直接发送到服务器</string>
<string name="pref_hide_local_cursor">隐藏本地指针</string>
<string name="pref_mouse_back">返回按钮</string>
<string name="pref_mouse_back_action_default">默认</string>
<string name="pref_mouse_back_action_right">右键单击</string>
<string name="pref_vk">虚拟按键</string>
<string name="pref_vk_show_all">显示所有按键</string>
<string name="pref_km">按键映射</string>
<string name="pref_km_back_to_escape_summary">不影响导航栏</string>
<string name="pref_fullscreen">全屏</string>
<string name="pref_enable_pip">画中画</string>
<string name="pref_toolbar_alignment">对齐</string>
<string name="pref_toolbar_alignment_option_start">开始</string>
<string name="pref_toolbar_alignment_option_end">结尾</string>
<string name="pref_zoom">缩放</string>
<string name="pref_zoom_min">最小</string>
<string name="pref_clipboard_sync">剪贴板同步</string>
<string name="pref_credential">登录</string>
<string name="pref_cred_autocomplete">自动完成</string>
<string name="pref_cred_autocomplete_summary">连接到新服务器时建议保存登录信息</string>
<string name="pref_discovery">发现</string>
<string name="pref_discovery_autorun">自动运行</string>
<string name="pref_rediscovery_indicator">突出显示重新发现的服务器</string>
<string name="pref_import_export">导入/导出 服务器</string>
<string name="title_clear">清除</string>
<string name="title_use_repeater">使用中继器</string>
<string name="pref_debugging">调试</string>
<string name="pref_logs">日志</string>
<string name="pref_keycode_map">键码映射</string>
<string name="pref_experimental_toolbar_swipe_close_summary">在远程会话期间,可以轻扫以关闭当前打开的工具栏</string>
<string name="pref_experimental_immersive_mode">完全沉浸式模式</string>
<string name="pref_experimental_immersive_mode_summary">在全屏模式下隐藏所有系统栏</string>
<string name="title_advanced">高级</string>
<string name="title_settings">设置</string>
<string name="title_export">导出</string>
<string name="title_key_compat_mode">发送旧式按键事件</string>
<string name="title_unknown_ssh_host">未知 SSH 主机</string>
<string name="title_github_repo">源码</string>
<string name="msg_loading">正在加载…</string>
<string name="pref_mouse_back_action_middle">中键单击</string>
<string name="title_continue">继续</string>
<string name="title_delete_servers_before_import">导入前删除当前服务器</string>
<string name="title_change">更改</string>
<string name="title_open_source_libraries">开源库</string>
<string name="title_report_bug">报告问题</string>
<string name="msg_export_auth_missing">你的设备不安全。在导出前打开屏幕锁定以启用身份验证。</string>
<string name="msg_about_app">快速安全的 VNC 客户端。远程查看和控制运行 VNC 服务器的任何设备。
\n
\n
\nAVNC 是一个开源和非盈利版权(copyleft)软件 — 在许多贡献者的帮助下制作的。</string>
<string name="msg_pip_not_supported">此设备不支持</string>
<string name="msg_key_compat_mode_help">一些服务器对 Unicode 的支持有限。旧式按键事件可以帮助输入非英语文本。</string>
<string name="tip_empty_server_list">服务器列表为空。
\n单击“<b>+</b>”添加服务器,或
\n使用顶部的地址栏直接连接。</string>
<string name="desc_discovered_list">自动发现的服务器列表</string>
<string name="desc_saved_servers_list">已保存的服务器列表</string>
<string name="pref_viewer_summary">画中画,缩放</string>
<string name="pref_input">输入</string>
<string name="pref_input_summary">手势、鼠标、虚拟键</string>
<string name="pref_tools">工具</string>
<string name="pref_experimental_summary">额外的好东西</string>
<string name="pref_prefer_advanced_editor">直接打开高级服务器选项</string>
<string name="pref_gesture_action_pan">平移本地帧</string>
<string name="pref_gesture_action_open_keyboard">打开键盘</string>
<string name="pref_sort_server_list">按名称对已保存的服务器进行排序</string>
<string name="pref_single_tap">单次敲击</string>
<string name="pref_vk_open_with_keyboard">同时打开键盘</string>
<string name="pref_mouse_passthrough_summary_off">将鼠标事件用于本地手势</string>
<string name="pref_toolbar">工具栏</string>
<string name="pref_zoom_max">最大</string>
<string name="pref_discovery_autorun_summary">在主页上时发现服务器</string>
<string name="pref_experimental_toolbar_swipe_close">轻扫以关闭工具栏抽屉</string>
<string name="title_default_security">默认安全度</string>
<string name="pref_swipe_sensitivity">调节灵敏度</string>
<string name="pref_km_language_switch_to_super">语言切换 → Super 键</string>
<string name="pref_km_back_to_escape">退格键 → Esc 键</string>
<string name="pref_km_right_alt_to_super">右 Alt 键 → Super (开始菜单)键</string>
<string name="msg_drag_gesture_help">为拖动手势分配一个动作将改变长按检测:
\n
\n<b>按住释放</b> → 长按
\n<b>按住滑动</b> → 拖动</string>
<string name="pref_orientation">屏幕方向</string>
<string name="pref_orientation_option_auto">自动</string>
<string name="pref_orientation_option_portrait">竖屏</string>
<string name="pref_orientation_option_landscape">横屏</string>
</resources>

View file

@ -19,6 +19,16 @@
<item>light</item>
</string-array>
<string-array name="orientation_entries">
<item>@string/pref_orientation_option_auto</item>
<item>@string/pref_orientation_option_portrait</item>
<item>@string/pref_orientation_option_landscape</item>
</string-array>
<string-array name="orientation_values">
<item>auto</item>
<item>portrait</item>
<item>landscape</item>
</string-array>
<string-array name="toolbar_alignment_entries">
<item>@string/pref_toolbar_alignment_option_start</item>
@ -29,15 +39,6 @@
<item>end</item>
</string-array>
<string-array name="tap1_action_entries">
<item>@string/pref_gesture_action_none</item>
<item>@string/pref_gesture_action_left_click</item>
</string-array>
<string-array name="tap1_action_values">
<item>none</item>
<item>left-click</item>
</string-array>
<string-array name="tap2_action_entries">
<item>@string/pref_gesture_action_none</item>
<item>@string/pref_gesture_action_right_click</item>
@ -109,4 +110,15 @@
<item>pan</item>
<item>remote-scroll</item>
</string-array>
<string-array name="mouse_back_action_entries">
<item>@string/pref_mouse_back_action_default</item>
<item>@string/pref_gesture_action_middle_click</item>
<item>@string/pref_gesture_action_right_click</item>
</string-array>
<string-array name="mouse_back_action_values">
<item>default</item>
<item>middle-click</item>
<item>right-click</item>
</string-array>
</resources>

View file

@ -27,6 +27,7 @@
<string name="title_export">Export</string>
<string name="title_import">Import</string>
<string name="title_view_only_mode">View-only</string>
<string name="title_default_security">Default security</string>
<string name="title_hide_remote_cursor">Hide remote pointer</string>
<string name="title_key_compat_mode">Send legacy key events</string>
<string name="title_use_ssh_tunnel">Use SSH tunnel</string>
@ -68,6 +69,7 @@
<string name="msg_invalid_vnc_uri">Invalid VNC URI</string>
<string name="msg_copied_to_clipboard">Copied</string>
<string name="msg_key_compat_mode_help">Some servers have limited Unicode support. Legacy key events can help input non-English text.</string>
<string name="msg_drag_gesture_help">Assigning an action to Drag gesture will change Long press detection:\n\n<b>Press-hold-release</b> → Long press\n<b>Press-hold-swipe</b> → Drag</string>
<string name="tip_empty_server_list">Server list is empty.\nClick \'<b>+</b>\' to add a server, or\nuse the top address bar to connect directly.</string>
<string name="tip_urlbar">You can enter IP address, hostname, or \'vnc://\' URI.</string>
<string name="tip_discovery">Discovery can automatically find supported servers\nrunning on your network.</string>
@ -80,6 +82,7 @@
<string name="desc_add_new_server_btn">Add server</string>
<string name="desc_save_discovered_server_btn">Save</string>
<string name="desc_discovery_btn">Discover servers</string>
<string name="desc_discovery_stop_btn">Stop discovering servers</string>
<string name="desc_discovery_running">Discovering servers on you network</string>
<string name="desc_keyboard_btn">Open keyboard</string>
<string name="desc_virtual_keys_btn">Show virtual keys</string>
@ -133,12 +136,17 @@
<string name="pref_long_press">Long press</string>
<string name="pref_swipe1">One-finger swipe</string>
<string name="pref_swipe2">Two-finger swipe</string>
<string name="pref_drag">Drag (long press and swipe)</string>
<string name="pref_drag">Drag</string>
<string name="pref_swipe_sensitivity">Swipe sensitivity</string>
<string name="pref_mouse">Mouse</string>
<string name="pref_mouse_passthrough">Mouse passthrough</string>
<string name="pref_mouse_passthrough_summary_off">Use mouse events for local gestures</string>
<string name="pref_mouse_passthrough_summary_on">Send mouse events directly to server</string>
<string name="pref_hide_local_cursor">Hide local (Android) pointer</string>
<string name="pref_hide_local_cursor">Hide local pointer</string>
<string name="pref_mouse_back">Back button</string>
<string name="pref_mouse_back_action_default">Default</string>
<string name="pref_mouse_back_action_right">Right click</string>
<string name="pref_mouse_back_action_middle">Middle click</string>
<string name="pref_vk">Virtual keys</string>
<string name="pref_vk_show_all">Show all keys</string>
<string name="pref_vk_open_with_keyboard">Open with keyboard</string>
@ -147,6 +155,10 @@
<string name="pref_km_language_switch_to_super">Language Switch → Super</string>
<string name="pref_km_back_to_escape">Back → Escape</string>
<string name="pref_km_back_to_escape_summary">Does not affect the navigation bar</string>
<string name="pref_orientation">Screen orientation</string>
<string name="pref_orientation_option_auto">Automatic</string>
<string name="pref_orientation_option_portrait">Portrait</string>
<string name="pref_orientation_option_landscape">Landscape</string>
<string name="pref_fullscreen">Fullscreen</string>
<string name="pref_enable_pip">Picture-in-picture</string>
<string name="pref_toolbar">Toolbar</string>
@ -161,20 +173,15 @@
<string name="pref_cred_autocomplete">Auto-completion</string>
<string name="pref_cred_autocomplete_summary">Suggest saved logins when connecting to new servers</string>
<string name="pref_discovery">Discovery</string>
<string name="pref_discovery_timeout">Timeout (seconds)</string>
<string name="pref_discovery_auto_start">Auto start</string>
<string name="pref_discovery_auto_start_summary">Discover servers when opening the app</string>
<string name="pref_discovery_restart">Restart</string>
<string name="pref_discovery_restart_summary">Restart discovery after it stops</string>
<string name="pref_discovery_restart_delay">Restart delay (minutes)</string>
<string name="pref_discovery_autorun">Autorun</string>
<string name="pref_discovery_autorun_summary">Discover servers while on homepage</string>
<string name="pref_rediscovery_indicator">Highlight rediscovered servers</string>
<string name="pref_import_export">Import/Export servers</string>
<string name="pref_debugging">Debugging</string>
<string name="pref_logs">Logs</string>
<string name="pref_keycode_map">Keycode map</string>
<string name="pref_experimental_toolbar_swipe_close">Swipe to close toolbar drawer</string>
<string name="pref_experimental_toolbar_swipe_close_summary">During a remote session, you can swipe to close currently open Toolbar</string>
<string name="pref_experimental_indicator">Discovery indicator for saved Servers</string>
<string name="pref_experimental_indicator_summary">Highlights already saved servers, which are also found during discovery.\nRequires app restart.</string>
<string name="pref_experimental_immersive_mode">Full immersive mode</string>
<string name="pref_experimental_immersive_mode_summary">Hides all system bars in fullscreen mode</string>
</resources>

View file

@ -9,12 +9,6 @@
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"
app:title="@string/pref_experimental">
<SwitchPreference
app:defaultValue="false"
app:key="experimental_indicator"
app:summary="@string/pref_experimental_indicator_summary"
app:title="@string/pref_experimental_indicator" />
<SwitchPreference
app:defaultValue="true"
app:key="experimental_vnc_toolbar_swipe_close"
@ -22,7 +16,7 @@
app:title="@string/pref_experimental_toolbar_swipe_close" />
<SwitchPreference
app:defaultValue="false"
app:defaultValue="true"
app:key="viewer_immersive_mode"
app:summary="@string/pref_experimental_immersive_mode_summary"
app:title="@string/pref_experimental_immersive_mode" />

View file

@ -6,7 +6,8 @@
~ See COPYING.txt for more details.
-->
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:title="@string/pref_input">
<PreferenceCategory
@ -20,14 +21,6 @@
app:summaryOn="@string/pref_direct_touch_summary_on"
app:title="@string/pref_direct_touch" />
<ListPreference
app:defaultValue="left-click"
app:entries="@array/tap1_action_entries"
app:entryValues="@array/tap1_action_values"
app:key="gesture_single_tap"
app:title="@string/pref_single_tap"
app:useSimpleSummaryProvider="true" />
<ListPreference
app:defaultValue="double-click"
app:entries="@array/double_tap_action_entries"
@ -69,13 +62,26 @@
app:title="@string/pref_swipe2"
app:useSimpleSummaryProvider="true" />
<ListPreference
<com.gaurav.avnc.ui.prefs.ListPreferenceEx
app:defaultValue="none"
app:entries="@array/drag_action_entries"
app:entryValues="@array/drag_action_values"
app:key="gesture_drag"
app:title="@string/pref_drag"
app:useSimpleSummaryProvider="true" />
app:useSimpleSummaryProvider="true"
app:widgetLayout="@layout/help_btn" />
<SeekBarPreference
android:max="15"
app:defaultValue="10"
app:key="gesture_swipe_sensitivity"
app:min="5"
app:title="@string/pref_swipe_sensitivity" />
<SwitchPreference
android:defaultValue="true"
app:key="natural_scrolling"
app:title="Natural scrolling" />
</PreferenceCategory>
<PreferenceCategory
@ -93,6 +99,19 @@
app:defaultValue="false"
app:key="hide_local_cursor"
app:title="@string/pref_hide_local_cursor" />
<SwitchPreference
app:defaultValue="false"
app:key="hide_remote_cursor"
app:title="@string/title_hide_remote_cursor" />
<ListPreference
app:defaultValue="right-click"
app:entries="@array/mouse_back_action_entries"
app:entryValues="@array/mouse_back_action_values"
app:key="mouse_back"
app:title="@string/pref_mouse_back"
app:useSimpleSummaryProvider="true" />
</PreferenceCategory>
<PreferenceCategory

View file

@ -6,8 +6,7 @@
~ See COPYING.txt for more details.
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"
app:title="@string/pref_servers">
@ -32,38 +31,16 @@
app:icon="@drawable/ic_search"
app:title="@string/pref_discovery">
<SeekBarPreference
android:max="90"
app:defaultValue="10"
app:key="discovery_timeout"
app:min="5"
app:showSeekBarValue="true"
app:title="@string/pref_discovery_timeout"
app:updatesContinuously="true" />
<SwitchPreference
app:defaultValue="true"
app:key="discovery_autorun"
app:summary="@string/pref_discovery_autorun_summary"
app:title="@string/pref_discovery_autorun" />
<SwitchPreference
app:defaultValue="true"
app:key="discovery_auto_start"
app:summary="@string/pref_discovery_auto_start_summary"
app:title="@string/pref_discovery_auto_start" />
<SwitchPreference
android:dependency="discovery_auto_start"
app:defaultValue="false"
app:key="discovery_restart"
app:summary="@string/pref_discovery_restart_summary"
app:title="@string/pref_discovery_restart" />
<SeekBarPreference
android:dependency="discovery_restart"
android:max="30"
app:defaultValue="5"
app:key="discovery_restart_delay"
app:min="1"
app:showSeekBarValue="true"
app:title="@string/pref_discovery_restart_delay"
app:updatesContinuously="true" />
app:key="rediscovery_indicator"
app:title="@string/pref_rediscovery_indicator" />
</PreferenceCategory>
</PreferenceScreen>

View file

@ -10,6 +10,14 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
app:title="@string/pref_viewer">
<ListPreference
android:defaultValue="auto"
android:entries="@array/orientation_entries"
android:entryValues="@array/orientation_values"
android:key="viewer_orientation"
android:title="@string/pref_orientation"
app:useSimpleSummaryProvider="true" />
<SwitchPreference
app:defaultValue="true"
app:key="fullscreen_display"

View file

@ -21,4 +21,23 @@ allprojects {
task clean(type: Delete) {
delete rootProject.buildDir
}
}
/**
* Temporary workaround to enable CMake 3.10 compatibility for WolfSSL
*/
try {
def cl = file('extern/wolfssl/CMakeLists.txt')
def Fix1Old = 'cmake_minimum_required(VERSION 3.16)'
def Fix1New = 'cmake_minimum_required(VERSION 3.10)'
def Fix2Old = 'list(JOIN HEADER_EXCLUDE "|" EXCLUDED_HEADERS_REGEX)'
def Fix2New = 'string(REPLACE ";" "|" EXCLUDED_HEADERS_REGEX "${HEADER_EXCLUDE}")'
cl.text = cl.text.replace(Fix1Old, Fix1New).replace(Fix2Old, Fix2New)
gradle.buildFinished {
cl.text = cl.text.replace(Fix1New, Fix1Old).replace(Fix2New, Fix2Old)
}
} catch (FileNotFoundException ignored) {
}

@ -1 +1 @@
Subproject commit ecf021bc0d6f435daacff7c35ccaeef0145df1b9
Subproject commit c5f269eb9665435271c05fbcaf8721fa58e9eafa

2
extern/libvncserver vendored

@ -1 +1 @@
Subproject commit e5dc3d42b913ab8c3b757de572af708b441e669c
Subproject commit ee547c7a39a0c63b24140db5dd2d21d72c525252

2
extern/wolfssl vendored

@ -1 +1 @@
Subproject commit 7e01af012157bc20c840011a018619915380f05c
Subproject commit 57aac1c50b45275c7a99eca32ad985998b292dc8

View file

@ -0,0 +1,16 @@
AVNC ist ein Open Source VNC-Client für Android. Er ermöglicht es dir, jedes Gerät fernzusteuern, auf welchem ein VNC-Server läuft.
Funktionen:
- Material Design (inkl. Dark Theme)
- Konfigurierbare Gesten
- Starke Kodierung
- virtuelle Zusatztasten (z.B. Ctrl, Alt, Tab, Esc, Pfeiltasten...)
- Bild-in-Bild-Modus
- Zuschauermodus (View-Only)
- automatische Zeroconf-Serversuche im LAN
- TLS-Unterstützung (AnonTLS, VeNCrypt)
- SSH-Tunnel (VNC getunnelt durch SSH)
- Exportieren und Importieren der abgespeicherten Server
- Unterstützung für VNC-Repeater
- Synchronisation der Zwischenablage

View file

@ -0,0 +1 @@
Schneller und sicherer VNC-Client für Android

View file

@ -0,0 +1,9 @@
* Added client-side cursor rendering for improved responsiveness
* Viewer will now follow the pointer when Direct Mode is disabled
* Intercept Back key triggered by mouse/touchpad (by Tuan Pham)
* New preference for swipe sensitivity
* New language:
Turkish (by Fabriel & Oğuz Ersen)
Polish (by WaldiS)
Chinese (Simplified) (by wang)
* Note: 'Hide remote pointer' option has moved to Settings

View file

@ -0,0 +1 @@
Fix click position of Mouse Back actions

View file

@ -0,0 +1,4 @@
* New preference for natural scrolling
* New preference for screen orientation
* Pointer is now centered on screen when Direct Touch is off
* New language: German (by Pure Incompetence)

View file

@ -0,0 +1,3 @@
* Fixed black screen issue on some Tegra GPUs
* Excluded viewer's toolbar area from system back gesture
* New language: Russian

View file

@ -0,0 +1,3 @@
* Small improvement in connection startup speed
* Fixed a crash on first ever connection when toolbar was aligned to end
* Fixed a crash when entering picture-in-picture for servers with large aspect ratio

View file

@ -0,0 +1,9 @@
* Improved login auto-completion
* Improved server discovery
* New language:
Serbian (by Саша Петровић)
Ukrainian (by Andrij Mizyk)
* Updated language:
Norwegian Bokmål (by Allan Nordhøy)
Persian (by Eshagh Shahedany)
French (by J. Lavoie, Nathan)

View file

@ -1,2 +1,2 @@
* Improve app startup time
* Added a preference to sort Servers
* بهبود زمان راه‌اندازی برنامه
* افزودن یک اولویت برای مرتب‌سازی کارسازها

View file

@ -1,3 +1,3 @@
* Added Samsung touchpad support (by Tuan Pham)
* Added stylus/pen support
* Added 2-finger Tap gesture
* افزودن پشتیبانی از پد لمسی سامسونگ (توسط Tuan Pham)
* افزودن پشتیبانی از stylus/pen
* افزودن حرکت ضربهٔ دو-انگشتی

View file

@ -1,3 +1,3 @@
* Allow key mappings to SUPER/Window key (by Tuan Pham)
* Ensure virtual meta-keys are always released (by thechosenreader)
* Added diacritics (accent) support
* Added diacritics (accent) support

View file

@ -1,3 +1,3 @@
* Improved keyboard shortcuts support
* Improved diacritic (accent) support
* Added full immersive mode (experimental)
* Added full immersive mode (experimental)

View file

@ -0,0 +1,3 @@
* افزودن ترجمهٔ پرتغالی (برزیل) (توسط inkhorn)
* افزودن یک ترجیح برای پنهان کردن اشاره‌گر محلّی اندروید (توسط Henry H)
* برطرف کردن اداره کردن شناور قلم در برخی افزارها (by Henry H)

View file

@ -0,0 +1,8 @@
* افزودن حالت تاچ‌پد/ترک‌پک
* ضبط کردن کلید ویندوز و دگرساز+جهش در Samsung DeX
* بهینه‌سازی بزرگنمایی برای صفحه‌نمایش‌های عمودی دوردست
* برطرف کردن حساسیت حرکت کشیدن
* ترجمه: پرتغالی برزیل (توسط João Paulo)،
بوکملی نروژ (توسط Allan Nordhøy)،
فارسی (توسط Eshagh Shahedany)،
فرانسوی (توسط J. Lavoie)

View file

@ -0,0 +1,9 @@
* بهبود در تکمیل خودکار ورود به سامانه
* بهبود در اکتشاف کارسازها
* زبان‌های جدید:
صربستانی (توسط Саша Петровић)
اوکراینی (توسط Andrij Mizyk)
* زبان‌های به‌روز شده:
بوکمالی نروژ (توسط Allan Nordhøy)
فارسی (توسط Eshagh Shahedany)
فرانسوری (توسط J. Lavoie, Nathan)

View file

@ -0,0 +1,3 @@
* Renforcement du tunnel SSH
* Correction de la fuite du port de redirection
* Mise à jour des métadonnées de l'application

View file

@ -0,0 +1,2 @@
* Amélioration du temps de démarrage de l'application
* Ajout d'une préférence pour le tri des serveurs

View file

@ -0,0 +1,3 @@
* Ajout de la prise en charge du touchpad Samsung (par Tuan Pham)
* Ajout de la prise en charge du stylet/stylo
* Ajout du geste d'appui à 2 doigts

View file

@ -0,0 +1,3 @@
* Possibilité d'associer des touches aux touches SUPER/Window (par Tuan Pham)
* Nous nous sommes assurés que les touches virtuelles soient toujours relâchées (par thechosenreader)
* Ajout de la prise en charge des diacritiques (accents)

View file

@ -0,0 +1,3 @@
* Amélioration de la prise en charge des raccourcis clavier
* Amélioration de la prise en charge des diacritiques (accents)
* Ajout d'un mode immersif complet (expérimental)

View file

@ -0,0 +1,3 @@
* Ajout des traductions en portugais (BR) (par inkhorn)
* Ajout d'une préférence pour cacher le curseur local sur Android (par Henry H)
* Correction de la gestion du survol du stylet sur certains appareils (par Henry H)

View file

@ -0,0 +1,8 @@
* Ajout du mode trackpad/touchpad
* Capture de la touche Windows et Alt+Tab sur Samsung DeX
* Optimisation du zoom pour les écrans distants en mode portrait
* Correction de la sensibilité des gestes de glissement
* Traductions : Portugais - BR (par João Paulo),
Bokmål norvégien (par Allan Nordhøy),
Persan (par Eshagh Shahedany),
français (par J. Lavoie)

View file

@ -0,0 +1,9 @@
* Amélioration de l'autocomplétion à la connexion
* Amélioration de la découverte des serveurs
* Nouvelles langues :
Serbe (par Саша Петровић)
Ukrainien (par Andrij Mizyk)
* Langues mises à jour :
Norvégien Bokmål (par Allan Nordhøy)
Perse (par Eshagh Shahedany)
Français (par J. Lavoie, Nathan)

View file

@ -0,0 +1,16 @@
AVNC est un client VNC open source pour Android. Il vous permet de contrôler à distance tout appareil exécutant un serveur VNC.
Caractéristiques :
- Material Design (avec le thème sombre)
- Gestes configurables
- Encodage fort
- Touches virtuelles
- Mode Picture-in-Picture
- Mode visualisation seule
- Découverte du serveur Zeroconf
- Prise en charge de TLS (AnonTLS, VeNCrypt)
- Tunnel SSH (VNC sur SSH)
- Importation/Exportation de serveurs
- Support des répéteurs VNC
- Synchronisation du presse-papiers avec le serveur

View file

@ -0,0 +1 @@
Client pour VNC rapide et sécurisé pour Android

View file

@ -0,0 +1,3 @@
* Tilskrudd SSH-tunnel
* Fikset port-videresenderlekkasje
* Oppdatert program-metadata

View file

@ -0,0 +1,2 @@
* Forbedret programoppstartstid
* Innstilling for sortering av tjenere

View file

@ -0,0 +1,3 @@
* Tillagt Samsung-pekeflate-støtte (av Tuan Pham)
* Tillagt pekepinne/penn-støtte
* TIllagt håndvending for to-fingertrykk

View file

@ -0,0 +1,16 @@
AVNC er en fri VNC-klient for Android. Det tillater deg å kontrollere enheter som har en VNC-tjener annensteds hen.
Funksjoner:
- Materiell design (med mørk drakt)
- Oppsettbare håndvendinger
- Stram koding
- Virtuelt tastatur
- Bilde-i-bilde -modus
- Skrivebeskyttet modus
- Zeroconf-tjeneroppdagelse
- TLS-støtte (AnonTLS, VeNCrypt)
- SSH-tunnel (VNC over SSH)
- Importer/eksporter tjenere
- VNC-gjentagelsesstøtte
- Utklippstavlesyknronisering med tjener

Some files were not shown because too many files have changed in this diff Show more