mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-10-21 22:54:48 -04:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d0b1a42b9 | ||
|
|
dafe5f13b5 | ||
|
|
4e25611f70 | ||
|
|
01f2463603 |
No files matched your search
@@ -321,17 +321,6 @@ jobs:
|
||||
install -Dm644 tools/mangoapp/mangoapp "$D/usr/local/bin/mangoapp"
|
||||
rm -rf mango-pkgs
|
||||
ls -l "$M"
|
||||
mkdir -p pw-pkgs
|
||||
grep -v '^#' tools/pipewire/packages.txt | while read -r sum url; do
|
||||
[ -n "$url" ] || continue
|
||||
curl -fsSL --retry 3 -o pw-pkgs/pkg.tar.zst "$url"
|
||||
echo "$sum pw-pkgs/pkg.tar.zst" | sha256sum -c -
|
||||
tar --use-compress-program=unzstd -xf pw-pkgs/pkg.tar.zst -C pw-pkgs
|
||||
rm pw-pkgs/pkg.tar.zst
|
||||
done
|
||||
tools/pipewire/assemble.sh pw-pkgs "$D/usr/local/lib/droiddeck-pipewire.tzst"
|
||||
rm -rf pw-pkgs
|
||||
ls -l "$D/usr/local/lib/droiddeck-pipewire.tzst"
|
||||
T="$D/usr/local/lib/droiddeck-msitools"
|
||||
mkdir -p "$T" msi-pkgs
|
||||
grep -v '^#' tools/msitools/packages.txt | while read -r sum url; do
|
||||
@@ -489,8 +478,6 @@ jobs:
|
||||
/tmp/test-xr-tracking
|
||||
g++ -std=c++17 -Wall -Wextra -Werror tools/vr/tests/test_xr_touch.cpp -o /tmp/test-xr-touch
|
||||
/tmp/test-xr-touch
|
||||
g++ -std=c++17 -Wall -Wextra -Werror tools/vr/tests/test_pad_controller_config.cpp -o /tmp/test-pad-controller-config
|
||||
/tmp/test-pad-controller-config
|
||||
g++ -std=c++17 -Wall -Wextra -Werror -pthread tools/vr/tests/test_xr_vsync.cpp -o /tmp/test-xr-vsync
|
||||
/tmp/test-xr-vsync
|
||||
g++ -std=c++17 -Wall -Wextra -Werror -pthread tools/vr/tests/test_xr_frames.cpp -o /tmp/test-xr-frames
|
||||
|
||||
@@ -21,7 +21,7 @@ On Android 14 and newer, before Steam launches, you must turn on **Disable child
|
||||
|
||||
## Smart glasses
|
||||
|
||||
Use **Setup → Smart glasses** for app-wide virtual-screen settings across DroidDeck menus, launchers, desktop sessions, and games. Supported VITURE, RayNeo, XREAL, and Rokid connections provide 3DoF; Luma Ultra adds optional experimental 6DoF and stereo-assisted hand/Joy-Con tracking. Model support and SDK requirements differ: see the [smart-glasses support matrix](docs/smart-glasses-support.md) for supported devices, controls, setup, and limitations.
|
||||
Use **Setup → Smart glasses** for app-wide virtual-screen settings across DroidDeck menus, launchers, desktop sessions, and games. Supported VITURE, RayNeo, and XREAL connections provide 3DoF; Luma Ultra adds optional experimental 6DoF and stereo-assisted hand/Joy-Con tracking. Model support and SDK requirements differ: see the [smart-glasses support matrix](docs/smart-glasses-support.md) for supported devices, controls, setup, and limitations.
|
||||
|
||||
## Community
|
||||
|
||||
@@ -49,10 +49,10 @@ Compatibility and performance vary by device; hardware validation is limited. De
|
||||
|
||||
GPL-3.0. Runtime, shim, input, and controller work build on WinNative and Bannerlator (maxjivi05). Epic Games and GOG support build on [Phobos’s](https://github.com/phobos665) adaptation of [Legendary](https://github.com/legendary-gl/legendary) and [gogdl](https://github.com/Heroic-Games-Launcher/heroic-gogdl), respectively. The registry entries a GOG game's install script (`goggame-<id>.script`) sets are read the way [Heroic Games Launcher](https://github.com/Heroic-Games-Launcher/HeroicGamesLauncher)'s GOG setup (GPL-3.0) reads them. LSFG frame generation is from the work of Camille LaVey and the [Eden](https://eden-emu.dev) emulator project, following [lsfg-vk](https://github.com/PancakeTAS/lsfg-vk), ported to WinNative and DroidDeck by [@maxjivi05](https://github.com/maxjivi05); it needs your own copy of [Lossless Scaling](https://store.steampowered.com/app/993090/) and ships none of its shaders. x86 AppImages, and any whose own runtime cannot unpack them, are unpacked with [uruntime](https://github.com/VHSgunzo/uruntime) by VHSgunzo (MIT), shipped unmodified with its licence, which also starts the arm64 emulator AppImages whose own runtime cannot run its extractor on Android. The Steam client's audio goes through [@The412Banner](https://github.com/The412Banner)'s [DirectAudio](https://github.com/The412Banner/directaudio) PulseAudio sink (LGPL-2.1-or-later), built from `tools/directaudio/sink` with its request size kept to its ring so clients are not asked to queue 100 ms ahead. mmap ring-buffer controller input was developed by ribbit384 ([WinNative #345](https://github.com/WinNative-Emu/WinNative/pull/345)). Why the x86-64 Steam client refused the arm64 client's sign-in was checked by reading Steam's saved token with the `ConnectCache` format from [deadlock-api-ingest](https://github.com/deadlock-api/deadlock-api-ingest). x86 programs that link OpenSSL 3 under FEX use the copy in the [Steam Linux Runtime 4.0](https://gitlab.steamos.cloud/steamrt/steamrt) platform. Hooks written with `WriteProcessMemory` into code [FEX](https://github.com/FEX-Emu/FEX) has translated go through `/proc/<pid>/mem` again when the kernel refuses `process_vm_writev`, as [Wine](https://gitlab.winehq.org/wine/wine)'s `server/ptrace.c` wrote them before Wine 11. x86 Linux games under FEX get the same `/etc/asound.conf` that [pressure-vessel](https://gitlab.steamos.cloud/steamrt/steam-runtime-tools) writes into the Steam Linux Runtime, so ALSA plays through PulseAudio. Games render with [Mesa](https://gitlab.freedesktop.org/mesa/mesa) Turnip's own per-render-pass choice between tile memory and system memory, while the Steam client keeps `TU_DEBUG=sysmem`; [FEX](https://github.com/FEX-Emu/FEX)'s code disk cache was checked in its source and is already on through Proton. See [LICENSE](LICENSE). Steam and Proton belong to Valve Corporation; this project is not affiliated with Valve.
|
||||
|
||||
XR glasses support builds on WinNative's VITURE glasses work. VITURE protocol details were checked against [XRLinuxDriver](https://github.com/wheaney/XRLinuxDriver) by [@wheaney](https://github.com/wheaney), including the VITURE protocol header it ships, [ar-drivers-rs](https://github.com/badicsalex/ar-drivers-rs) by [@badicsalex](https://github.com/badicsalex) and the [go-macos/iokit VITURE package](https://pkg.go.dev/github.com/go-macos/iokit/viture); the optional VITURE SDK belongs to VITURE and is not included. RayNeo support adapts [@informalTechCode](https://github.com/informalTechCode)'s [RayNeo-Air-Series-3DoF-Scaffold](https://github.com/informalTechCode/RayNeo-Air-Series-3DoF-Scaffold) (Apache-2.0) and references [@verncat](https://github.com/verncat)'s [RayNeo-Air-3S-Pro-OpenVR](https://github.com/verncat/RayNeo-Air-3S-Pro-OpenVR) (MIT) and [Monado](https://gitlab.freedesktop.org/monado/monado) (BSL-1.0); see [docs/third-party/rayneo](docs/third-party/rayneo/README.md). XREAL 1S support references [XRLinuxDriver](https://github.com/wheaney/XRLinuxDriver) and [xrealOneDeviceKit](https://github.com/wheaney/xrealOneDeviceKit) by @wheaney, [xreal_one_driver](https://github.com/rohitsangwan01/xreal_one_driver) by [@rohitsangwan01](https://github.com/rohitsangwan01) (MIT) and [xr-tools](https://github.com/0xcaff/xr-tools) by [@0xcaff](https://github.com/0xcaff). Rokid support follows the Rokid protocol notes in [ar-drivers-rs](https://github.com/badicsalex/ar-drivers-rs) and @badicsalex's [Void Computing write-ups](https://voidcomputing.hu/blog/worse-better-prettier/), and [Monado](https://gitlab.freedesktop.org/monado/monado)'s Rokid driver. The REDMAGIC glasses-display findings draw on [@mekhontsev](https://github.com/mekhontsev)'s [MagicDesk investigation](https://github.com/mekhontsev/magicdesk/blob/main/docs/xr-resolution-fix.md) and [@Coding-BR](https://github.com/Coding-BR)'s [decompiled Nubia kernel](https://github.com/Coding-BR/android_kernel_nubia_sm8850_qwjujube). VITURE, RayNeo, XREAL, Rokid and REDMAGIC are trademarks of their owners; this project is not affiliated with them.
|
||||
XR glasses support builds on WinNative's VITURE glasses work. VITURE protocol details were checked against [XRLinuxDriver](https://github.com/wheaney/XRLinuxDriver) by [@wheaney](https://github.com/wheaney), including the VITURE protocol header it ships, [ar-drivers-rs](https://github.com/badicsalex/ar-drivers-rs) by [@badicsalex](https://github.com/badicsalex) and the [go-macos/iokit VITURE package](https://pkg.go.dev/github.com/go-macos/iokit/viture); the optional VITURE SDK belongs to VITURE and is not included. RayNeo support adapts [@informalTechCode](https://github.com/informalTechCode)'s [RayNeo-Air-Series-3DoF-Scaffold](https://github.com/informalTechCode/RayNeo-Air-Series-3DoF-Scaffold) (Apache-2.0) and references [@verncat](https://github.com/verncat)'s [RayNeo-Air-3S-Pro-OpenVR](https://github.com/verncat/RayNeo-Air-3S-Pro-OpenVR) (MIT) and [Monado](https://gitlab.freedesktop.org/monado/monado) (BSL-1.0); see [docs/third-party/rayneo](docs/third-party/rayneo/README.md). XREAL 1S support references [XRLinuxDriver](https://github.com/wheaney/XRLinuxDriver) and [xrealOneDeviceKit](https://github.com/wheaney/xrealOneDeviceKit) by @wheaney, [xreal_one_driver](https://github.com/rohitsangwan01/xreal_one_driver) by [@rohitsangwan01](https://github.com/rohitsangwan01) (MIT) and [xr-tools](https://github.com/0xcaff/xr-tools) by [@0xcaff](https://github.com/0xcaff). The REDMAGIC glasses-display findings draw on [@mekhontsev](https://github.com/mekhontsev)'s [MagicDesk investigation](https://github.com/mekhontsev/magicdesk/blob/main/docs/xr-resolution-fix.md) and [@Coding-BR](https://github.com/Coding-BR)'s [decompiled Nubia kernel](https://github.com/Coding-BR/android_kernel_nubia_sm8850_qwjujube). VITURE, RayNeo, XREAL and REDMAGIC are trademarks of their owners; this project is not affiliated with them.
|
||||
|
||||
Joy-Con VR controllers port the arm model of Google's [Google VR SDK for Unity](https://github.com/googlevr/gvr-unity-sdk) (Apache-2.0), adapt the hand skeleton of Valve's [OpenVR](https://github.com/ValveSoftware/openvr) `handskeletonsimulation` sample (BSD-3-Clause), which follows Moshi Turner's hand simulation in [Monado](https://gitlab.freedesktop.org/monado/monado), and use the Index controller offsets from Monado's `vive_poses.c` (BSL-1.0). Joy-Con behaviour was researched in Linux's [hid-nintendo](https://github.com/torvalds/linux/blob/master/drivers/hid/hid-nintendo.c) driver by Daniel J. Ogorchock, [@dekuNukem](https://github.com/dekuNukem)'s [Nintendo_Switch_Reverse_Engineering](https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering), [SDL](https://github.com/libsdl-org/SDL) and the Android Open Source Project, and checked against [ALVR](https://github.com/alvr-org/ALVR), [OpenComposite](https://gitlab.com/znixian/OpenOVR), [PSMoveSteamVRBridge](https://github.com/HipsterSloth/PSMoveSteamVRBridge), [@F8LEFT](https://github.com/F8LEFT)'s [JoyConVr](https://github.com/F8LEFT/JoyConVr), [@krnelpan1c](https://github.com/krnelpan1c)'s [Daydream-Controller-SteamVR](https://github.com/krnelpan1c/Daydream-Controller-SteamVR), [@yoyobuae](https://github.com/yoyobuae)'s [joyconvr](https://github.com/yoyobuae/joyconvr), [Moonlight](https://github.com/moonlight-stream/moonlight-android) and [Dolphin](https://github.com/dolphin-emu/dolphin); see [docs/third-party/joycon](docs/third-party/joycon/README.md). Nintendo Switch and Joy-Con are trademarks of Nintendo; this project is not affiliated with Nintendo.
|
||||
|
||||
Camera hand tracking runs Google's [MediaPipe](https://github.com/google-ai-edge/mediapipe) Tasks Vision library with its Pose Landmarker (Lite) model, BlazePose GHUM 3D, and its Hand Landmarker model (all Apache-2.0), on frames from AndroidX [CameraX](https://developer.android.com/jetpack/androidx/releases/camera) (Apache-2.0); MediaPipe's usage reports are switched off. The design was checked against [@F8LEFT](https://github.com/F8LEFT)'s [JoyConVr](https://github.com/F8LEFT/JoyConVr) camera app (MIT), and the short Joy-Con movements follow the accelerometer mode of the [Google VR SDK for Unity](https://github.com/googlevr/gvr-unity-sdk)'s arm model, Eric Foxlin's zero-velocity updates and Frosio, Pedersini and Borghese's accelerometer autocalibration, and the camera and Joy-Con are joined by a Kalman filter (R. E. Kalman, 1960). Bare camera hands take their gestures after [ALVR](https://github.com/alvr-org/ALVR)'s `hand_gestures.rs` (MIT) and Meta Quest's menu pinch, with distances checked on photos from the [HaGRID](https://github.com/hukenovs/hagrid) gesture set (CC BY-SA 4.0, not included), fit the palm with Horn's quaternion method (1987) and smooth it with the 1€ filter (Casiez, Roussel and Vogel, 2012); the white glove model follows Valve's OpenVR render model reference and Index controller poses, is meshed with surface nets (Gibson, 1998; Mikola Lysenko) and blends and shades it with Inigo Quilez's smooth minimum and distance-field ambient occlusion, porting his polygon distance (MIT); see [docs/third-party/handtracking](docs/third-party/handtracking/README.md).
|
||||
|
||||
The Store installs and runs apps with [Flatpak](https://github.com/flatpak/flatpak) (LGPL-2.1). Its sandbox goes through DroidDeck's own bubblewrap shim, which follows how Flatpak hands bwrap its environment. Chromium and Electron apps from Flathub start their browser processes through [zypak](https://github.com/refi64/zypak) by [@refi64](https://github.com/refi64) (BSD-3-Clause); its source was read to see that it always spawns them through Flatpak's spawn portal, which is why DroidDeck starts them directly without Chromium's sandbox instead.
|
||||
The Store installs and runs apps with [Flatpak](https://github.com/flatpak/flatpak) (LGPL-2.1). Its sandbox goes through DroidDeck's own bubblewrap shim, which follows how Flatpak hands bwrap its environment.
|
||||
@@ -177,6 +177,15 @@
|
||||
android:resizeableActivity="true"
|
||||
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|keyboard|keyboardHidden|navigation|uiMode|density" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.SecondScreenActivity"
|
||||
android:exported="false"
|
||||
android:excludeFromRecents="true"
|
||||
android:theme="@style/SessionTheme"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:resizeableActivity="true"
|
||||
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|keyboard|keyboardHidden|navigation|uiMode|density" />
|
||||
|
||||
<activity
|
||||
android:name=".gpu.SteamVrRestartActivity"
|
||||
android:exported="false"
|
||||
|
||||
@@ -39,10 +39,6 @@ target_link_libraries(deviceinfo dl)
|
||||
|
||||
add_library(termux SHARED termux/termux.c)
|
||||
|
||||
add_library(ddvenc SHARED venc/venc_server.cpp)
|
||||
target_compile_features(ddvenc PRIVATE cxx_std_17)
|
||||
target_link_libraries(ddvenc mediandk log)
|
||||
|
||||
# The optional proprietary SDK is supplied locally; its headers/libraries are not vendored.
|
||||
if(DROIDDECK_VITURE_SDK_DIR)
|
||||
add_library(droiddeck_viture SHARED viture/sdk_bridge.cpp)
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
#ifndef DROIDDECK_VENC_PROTO_H
|
||||
#define DROIDDECK_VENC_PROTO_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* SOCK_SEQPACKET between the guest's Remote Play hooks and the app: the emulated /dev/video-enc0 and
|
||||
* MediaCodec encoder, and remote touches. A config carries the buffer memfds (inputs first) as SCM_RIGHTS;
|
||||
* every frame gets one reply, touches get none. */
|
||||
|
||||
#define VENC_MAX_BUFFERS 16
|
||||
|
||||
enum venc_msg { VENC_CONFIG = 1, VENC_FRAME = 2, VENC_DONE = 3, VENC_BITRATE = 4, VENC_TOUCH = 5 };
|
||||
enum venc_touch_action { VENC_TOUCH_DOWN = 0, VENC_TOUCH_MOVE = 1, VENC_TOUCH_UP = 2 };
|
||||
enum venc_codec { VENC_H264 = 0, VENC_HEVC = 1 };
|
||||
|
||||
struct venc_config {
|
||||
uint32_t type;
|
||||
uint32_t codec;
|
||||
uint32_t width, height, stride, rows;
|
||||
uint32_t fps_num, fps_den;
|
||||
uint32_t bitrate, peak_bitrate, vbr;
|
||||
uint32_t gop;
|
||||
int32_t profile, level;
|
||||
uint32_t inputs, outputs;
|
||||
uint32_t input_size, output_size;
|
||||
};
|
||||
|
||||
struct venc_frame {
|
||||
uint32_t type;
|
||||
uint32_t input, output;
|
||||
uint32_t bytes;
|
||||
uint32_t keyframe;
|
||||
int64_t pts_us;
|
||||
};
|
||||
|
||||
struct venc_done {
|
||||
uint32_t type;
|
||||
int32_t status;
|
||||
uint32_t bytes;
|
||||
uint32_t keyframe;
|
||||
};
|
||||
|
||||
struct venc_bitrate {
|
||||
uint32_t type;
|
||||
uint32_t bitrate;
|
||||
};
|
||||
|
||||
/* Remote Play's touch-as-mouse turned back into a touch; x and y are fractions of the screen in 1/65535. */
|
||||
struct venc_touch {
|
||||
uint32_t type;
|
||||
uint32_t action;
|
||||
uint32_t x, y;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,346 +0,0 @@
|
||||
#include <android/log.h>
|
||||
#include <dlfcn.h>
|
||||
#include <jni.h>
|
||||
#include <media/NdkMediaCodec.h>
|
||||
#include <media/NdkMediaFormat.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "venc_proto.h"
|
||||
|
||||
#define LOG(...) __android_log_print(ANDROID_LOG_INFO, "DroidDeckVenc", __VA_ARGS__)
|
||||
|
||||
namespace {
|
||||
|
||||
struct Mapping {
|
||||
uint8_t *data = nullptr;
|
||||
size_t size = 0;
|
||||
};
|
||||
|
||||
constexpr int32_t kColorFormatYuv420SemiPlanar = 21;
|
||||
constexpr uint32_t kBufferFlagKeyFrame = 1;
|
||||
constexpr uint32_t kBufferFlagCodecConfig = 2;
|
||||
constexpr int kTouchWidth = 1920, kTouchHeight = 1080; // InputSpace.kt
|
||||
constexpr int kRemoteTouchId = 31; // the compositor's last touch slot (MAX_TOUCH_POINTS 32)
|
||||
|
||||
int32_t avc_profile(int32_t v4l2) {
|
||||
switch (v4l2) {
|
||||
case 0: return 1;
|
||||
case 1: return 0x10000;
|
||||
case 2: return 2;
|
||||
case 3: return 4;
|
||||
case 4: return 8;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
class Session {
|
||||
public:
|
||||
explicit Session(int fd) : fd_(fd) {}
|
||||
~Session() {
|
||||
release();
|
||||
close(fd_);
|
||||
}
|
||||
|
||||
void run() {
|
||||
for (;;) {
|
||||
uint8_t buf[256];
|
||||
int fds[VENC_MAX_BUFFERS * 2];
|
||||
int nfds = 0;
|
||||
ssize_t n = receive(buf, sizeof buf, fds, &nfds);
|
||||
if (n <= 0) return;
|
||||
uint32_t type;
|
||||
memcpy(&type, buf, sizeof type);
|
||||
if (type == VENC_CONFIG && n == (ssize_t)sizeof(venc_config)) {
|
||||
venc_config c;
|
||||
memcpy(&c, buf, sizeof c);
|
||||
bool ok = configure(c, fds, nfds);
|
||||
for (int i = 0; i < nfds; i++) close(fds[i]);
|
||||
reply(ok ? 0 : -1, 0, false);
|
||||
} else if (type == VENC_FRAME && n == (ssize_t)sizeof(venc_frame)) {
|
||||
venc_frame f;
|
||||
memcpy(&f, buf, sizeof f);
|
||||
uint32_t bytes = 0;
|
||||
bool key = false;
|
||||
bool ok = encode(f, &bytes, &key);
|
||||
reply(ok ? 0 : -1, bytes, key);
|
||||
} else if (type == VENC_TOUCH && n == (ssize_t)sizeof(venc_touch)) {
|
||||
venc_touch t;
|
||||
memcpy(&t, buf, sizeof t);
|
||||
touch(t);
|
||||
for (int i = 0; i < nfds; i++) close(fds[i]);
|
||||
} else if (type == VENC_BITRATE && n == (ssize_t)sizeof(venc_bitrate)) {
|
||||
venc_bitrate b;
|
||||
memcpy(&b, buf, sizeof b);
|
||||
set_bitrate(b.bitrate);
|
||||
for (int i = 0; i < nfds; i++) close(fds[i]);
|
||||
} else {
|
||||
LOG("unexpected message %u of %zd bytes; closing", type, n);
|
||||
for (int i = 0; i < nfds; i++) close(fds[i]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
ssize_t receive(uint8_t *buf, size_t len, int *fds, int *nfds) {
|
||||
char control[CMSG_SPACE(sizeof(int) * VENC_MAX_BUFFERS * 2)];
|
||||
iovec iov = {buf, len};
|
||||
msghdr m = {};
|
||||
m.msg_iov = &iov;
|
||||
m.msg_iovlen = 1;
|
||||
m.msg_control = control;
|
||||
m.msg_controllen = sizeof control;
|
||||
ssize_t n = recvmsg(fd_, &m, MSG_CMSG_CLOEXEC);
|
||||
for (cmsghdr *c = CMSG_FIRSTHDR(&m); n > 0 && c; c = CMSG_NXTHDR(&m, c)) {
|
||||
if (c->cmsg_level != SOL_SOCKET || c->cmsg_type != SCM_RIGHTS) continue;
|
||||
int count = (int)((c->cmsg_len - CMSG_LEN(0)) / sizeof(int));
|
||||
memcpy(fds, CMSG_DATA(c), sizeof(int) * std::min(count, VENC_MAX_BUFFERS * 2));
|
||||
*nfds = std::min(count, VENC_MAX_BUFFERS * 2);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
void reply(int32_t status, uint32_t bytes, bool key) {
|
||||
venc_done d = {VENC_DONE, status, bytes, key ? 1u : 0u};
|
||||
send(fd_, &d, sizeof d, MSG_NOSIGNAL);
|
||||
}
|
||||
|
||||
void release() {
|
||||
if (codec_) {
|
||||
AMediaCodec_stop(codec_);
|
||||
AMediaCodec_delete(codec_);
|
||||
codec_ = nullptr;
|
||||
}
|
||||
for (auto &m : inputs_) munmap(m.data, m.size);
|
||||
for (auto &m : outputs_) munmap(m.data, m.size);
|
||||
inputs_.clear();
|
||||
outputs_.clear();
|
||||
header_.clear();
|
||||
}
|
||||
|
||||
AMediaCodec *create(const venc_config &c, bool with_profile) {
|
||||
const char *mime = c.codec == VENC_HEVC ? "video/hevc" : "video/avc";
|
||||
AMediaCodec *codec = AMediaCodec_createEncoderByType(mime);
|
||||
if (!codec) return nullptr;
|
||||
float fps = c.fps_num ? (float)c.fps_den / (float)c.fps_num : 60.0f;
|
||||
AMediaFormat *f = AMediaFormat_new();
|
||||
AMediaFormat_setString(f, AMEDIAFORMAT_KEY_MIME, mime);
|
||||
AMediaFormat_setInt32(f, AMEDIAFORMAT_KEY_WIDTH, (int32_t)c.width);
|
||||
AMediaFormat_setInt32(f, AMEDIAFORMAT_KEY_HEIGHT, (int32_t)c.height);
|
||||
AMediaFormat_setInt32(f, AMEDIAFORMAT_KEY_COLOR_FORMAT, kColorFormatYuv420SemiPlanar);
|
||||
AMediaFormat_setInt32(f, AMEDIAFORMAT_KEY_BIT_RATE, (int32_t)std::max(c.bitrate, 500000u));
|
||||
AMediaFormat_setInt32(f, "bitrate-mode", c.vbr ? 1 : 2);
|
||||
AMediaFormat_setFloat(f, AMEDIAFORMAT_KEY_FRAME_RATE, fps);
|
||||
AMediaFormat_setFloat(f, "operating-rate", fps);
|
||||
AMediaFormat_setInt32(f, "priority", 0);
|
||||
AMediaFormat_setInt32(f, "latency", 1);
|
||||
AMediaFormat_setInt32(f, "max-bframes", 0);
|
||||
AMediaFormat_setInt32(f, "prepend-sps-pps-to-idr-frames", 1);
|
||||
float gop_seconds = c.gop && c.gop < 0xffff ? (float)c.gop / fps : 3600.0f;
|
||||
AMediaFormat_setFloat(f, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, std::max(gop_seconds, 0.1f));
|
||||
if (with_profile) {
|
||||
int32_t profile = c.codec == VENC_HEVC ? (c.profile == 2 ? 2 : 1) : avc_profile(c.profile);
|
||||
if (profile) AMediaFormat_setInt32(f, "profile", profile);
|
||||
}
|
||||
media_status_t s = AMediaCodec_configure(codec, f, nullptr, nullptr, AMEDIACODEC_CONFIGURE_FLAG_ENCODE);
|
||||
AMediaFormat_delete(f);
|
||||
if (s == AMEDIA_OK) s = AMediaCodec_start(codec);
|
||||
if (s != AMEDIA_OK) {
|
||||
AMediaCodec_delete(codec);
|
||||
return nullptr;
|
||||
}
|
||||
return codec;
|
||||
}
|
||||
|
||||
bool configure(const venc_config &c, const int *fds, int nfds) {
|
||||
release();
|
||||
if (c.inputs + c.outputs != (uint32_t)nfds || c.width < 64 || c.height < 64 || c.stride < c.width ||
|
||||
c.rows < c.height || (uint64_t)c.stride * c.rows * 3 / 2 > c.input_size) {
|
||||
LOG("bad config: %u+%u buffers for %d fds, %ux%u stride %u", c.inputs, c.outputs, nfds, c.width, c.height,
|
||||
c.stride);
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < nfds; i++) {
|
||||
bool input = (uint32_t)i < c.inputs;
|
||||
size_t size = input ? c.input_size : c.output_size;
|
||||
void *p = mmap(nullptr, size, input ? PROT_READ : PROT_READ | PROT_WRITE, MAP_SHARED, fds[i], 0);
|
||||
if (p == MAP_FAILED) {
|
||||
LOG("could not map buffer %d: %s", i, strerror(errno));
|
||||
release();
|
||||
return false;
|
||||
}
|
||||
(input ? inputs_ : outputs_).push_back({(uint8_t *)p, size});
|
||||
}
|
||||
codec_ = create(c, true);
|
||||
if (!codec_) codec_ = create(c, false);
|
||||
if (!codec_) {
|
||||
LOG("no %s encoder for %ux%u", c.codec == VENC_HEVC ? "HEVC" : "H.264", c.width, c.height);
|
||||
release();
|
||||
return false;
|
||||
}
|
||||
config_ = c;
|
||||
dst_stride_ = (int32_t)c.width;
|
||||
dst_rows_ = (int32_t)c.height;
|
||||
using InputFormat = AMediaFormat *(*)(AMediaCodec *);
|
||||
static auto input_format = (InputFormat)dlsym(RTLD_DEFAULT, "AMediaCodec_getInputFormat");
|
||||
if (AMediaFormat *in = input_format ? input_format(codec_) : nullptr) {
|
||||
int32_t v;
|
||||
if (AMediaFormat_getInt32(in, "stride", &v) && v >= (int32_t)c.width) dst_stride_ = v;
|
||||
if (AMediaFormat_getInt32(in, "slice-height", &v) && v >= (int32_t)c.height) dst_rows_ = v;
|
||||
AMediaFormat_delete(in);
|
||||
}
|
||||
LOG("%s %ux%u at %.2f fps, %u kbit/s %s, input stride %d rows %d", c.codec == VENC_HEVC ? "HEVC" : "H.264",
|
||||
c.width, c.height, c.fps_num ? (double)c.fps_den / c.fps_num : 0.0, c.bitrate / 1000,
|
||||
c.vbr ? "VBR" : "CBR", dst_stride_, dst_rows_);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void touch(const venc_touch &t) {
|
||||
using SendTouch = void (*)(int, int, int, int);
|
||||
static SendTouch send_touch = [] {
|
||||
void *wayland = dlopen("libdroiddeckwayland.so", RTLD_NOW | RTLD_NOLOAD);
|
||||
auto fn = wayland ? (SendTouch)dlsym(wayland, "droiddeck_wayland_send_touch") : nullptr;
|
||||
if (!fn) LOG("remote touch unavailable: %s", dlerror());
|
||||
return fn;
|
||||
}();
|
||||
if (!send_touch || t.action > VENC_TOUCH_UP) return;
|
||||
int x = (int)((uint64_t)std::min(t.x, 65535u) * (kTouchWidth - 1) / 65535);
|
||||
int y = (int)((uint64_t)std::min(t.y, 65535u) * (kTouchHeight - 1) / 65535);
|
||||
send_touch((int)t.action, kRemoteTouchId, x, y);
|
||||
}
|
||||
|
||||
void set_bitrate(uint32_t bitrate) {
|
||||
if (!codec_) return;
|
||||
AMediaFormat *p = AMediaFormat_new();
|
||||
AMediaFormat_setInt32(p, "video-bitrate", (int32_t)std::max(bitrate, 500000u));
|
||||
AMediaCodec_setParameters(codec_, p);
|
||||
AMediaFormat_delete(p);
|
||||
}
|
||||
|
||||
bool encode(const venc_frame &f, uint32_t *bytes, bool *key) {
|
||||
if (!codec_ || f.input >= inputs_.size() || f.output >= outputs_.size()) return false;
|
||||
if (f.keyframe) {
|
||||
AMediaFormat *p = AMediaFormat_new();
|
||||
AMediaFormat_setInt32(p, "request-sync", 0);
|
||||
AMediaCodec_setParameters(codec_, p);
|
||||
AMediaFormat_delete(p);
|
||||
}
|
||||
ssize_t in = AMediaCodec_dequeueInputBuffer(codec_, 100000);
|
||||
if (in < 0) return false;
|
||||
size_t capacity = 0;
|
||||
uint8_t *dst = AMediaCodec_getInputBuffer(codec_, (size_t)in, &capacity);
|
||||
size_t need = (size_t)dst_stride_ * dst_rows_ + (size_t)dst_stride_ * ((config_.height + 1) / 2);
|
||||
if (!dst || capacity < need) return false;
|
||||
const uint8_t *src = inputs_[f.input].data;
|
||||
for (uint32_t y = 0; y < config_.height; y++)
|
||||
memcpy(dst + (size_t)y * dst_stride_, src + (size_t)y * config_.stride, config_.width);
|
||||
const uint8_t *src_uv = src + (size_t)config_.stride * config_.rows;
|
||||
uint8_t *dst_uv = dst + (size_t)dst_stride_ * dst_rows_;
|
||||
for (uint32_t y = 0; y < (config_.height + 1) / 2; y++)
|
||||
memcpy(dst_uv + (size_t)y * dst_stride_, src_uv + (size_t)y * config_.stride, config_.width);
|
||||
if (AMediaCodec_queueInputBuffer(codec_, (size_t)in, 0, need, (uint64_t)std::max<int64_t>(f.pts_us, 0), 0) !=
|
||||
AMEDIA_OK)
|
||||
return false;
|
||||
Mapping &out = outputs_[f.output];
|
||||
for (int tries = 0; tries < 20; tries++) {
|
||||
AMediaCodecBufferInfo info;
|
||||
ssize_t o = AMediaCodec_dequeueOutputBuffer(codec_, &info, 20000);
|
||||
if (o == AMEDIACODEC_INFO_TRY_AGAIN_LATER || o == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED ||
|
||||
o == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED)
|
||||
continue;
|
||||
if (o < 0) return false;
|
||||
size_t size = 0;
|
||||
uint8_t *data = AMediaCodec_getOutputBuffer(codec_, (size_t)o, &size);
|
||||
const uint8_t *payload = data ? data + info.offset : nullptr;
|
||||
size_t length = data ? (size_t)info.size : 0;
|
||||
if (info.flags & kBufferFlagCodecConfig) {
|
||||
header_.assign(payload, payload + length);
|
||||
AMediaCodec_releaseOutputBuffer(codec_, (size_t)o, false);
|
||||
continue;
|
||||
}
|
||||
*key = (info.flags & kBufferFlagKeyFrame) != 0;
|
||||
bool prepend = *key && !header_.empty() &&
|
||||
(length < header_.size() || memcmp(payload, header_.data(), header_.size()) != 0);
|
||||
size_t total = length + (prepend ? header_.size() : 0);
|
||||
bool fits = payload && total <= out.size;
|
||||
if (fits) {
|
||||
if (prepend) memcpy(out.data, header_.data(), header_.size());
|
||||
memcpy(out.data + (prepend ? header_.size() : 0), payload, length);
|
||||
*bytes = (uint32_t)total;
|
||||
}
|
||||
AMediaCodec_releaseOutputBuffer(codec_, (size_t)o, false);
|
||||
return fits;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int fd_;
|
||||
AMediaCodec *codec_ = nullptr;
|
||||
venc_config config_ = {};
|
||||
int32_t dst_stride_ = 0, dst_rows_ = 0;
|
||||
std::vector<Mapping> inputs_, outputs_;
|
||||
std::vector<uint8_t> header_;
|
||||
};
|
||||
|
||||
std::atomic<int> g_listener{-1};
|
||||
|
||||
void serve(int listener) {
|
||||
for (;;) {
|
||||
int fd = accept4(listener, nullptr, nullptr, SOCK_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
return;
|
||||
}
|
||||
std::thread([fd] {
|
||||
Session s(fd);
|
||||
s.run();
|
||||
}).detach();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_droiddeck_launcher_session_VideoEncodeBridge_nativeStart(JNIEnv *env, jobject, jstring jpath) {
|
||||
if (g_listener.load() >= 0) return JNI_TRUE;
|
||||
const char *path = env->GetStringUTFChars(jpath, nullptr);
|
||||
sockaddr_un addr = {};
|
||||
addr.sun_family = AF_UNIX;
|
||||
bool fits = strlen(path) < sizeof addr.sun_path;
|
||||
if (fits) strcpy(addr.sun_path, path);
|
||||
int fd = fits ? socket(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0) : -1;
|
||||
if (fd >= 0) {
|
||||
unlink(path);
|
||||
if (bind(fd, (sockaddr *)&addr, sizeof addr) != 0 || listen(fd, 4) != 0) {
|
||||
LOG("could not listen on %s: %s", path, strerror(errno));
|
||||
close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
}
|
||||
env->ReleaseStringUTFChars(jpath, path);
|
||||
if (fd < 0) return JNI_FALSE;
|
||||
g_listener.store(fd);
|
||||
std::thread(serve, fd).detach();
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_droiddeck_launcher_session_VideoEncodeBridge_nativeStop(JNIEnv *, jobject) {
|
||||
int fd = g_listener.exchange(-1);
|
||||
if (fd >= 0) {
|
||||
shutdown(fd, SHUT_RDWR);
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
@@ -11,46 +11,29 @@ import java.io.File
|
||||
|
||||
internal class DeckyMenu(private val activity: android.app.Activity, private val ui: android.os.Handler) {
|
||||
var deckyInstalled by mutableStateOf<String?>(null)
|
||||
var deckyLatest by mutableStateOf<DeckyManager.Release?>(null)
|
||||
var deckyBranch by mutableIntStateOf(DeckyManager.BRANCH_STABLE)
|
||||
private var deckyChannels = DeckyManager.ReleaseChannels(emptyList(), emptyList())
|
||||
var deckyReleases by mutableStateOf<List<DeckyManager.Release>>(emptyList())
|
||||
var deckyChecking by mutableStateOf(false)
|
||||
var deckyStage by mutableStateOf<String?>(null)
|
||||
var deckyPercent by mutableIntStateOf(-1)
|
||||
var deckyDownloadingBinary by mutableStateOf(false)
|
||||
var deckySupervisor by mutableStateOf(false)
|
||||
var deckyPendingZip by mutableStateOf<File?>(null)
|
||||
var deckyReleaseRequest = 0
|
||||
|
||||
fun refreshDecky() {
|
||||
val request = ++deckyReleaseRequest
|
||||
deckyBranch = DeckyManager.branch(activity)
|
||||
deckyChecking = true
|
||||
Thread({
|
||||
val channels = runCatching { DeckyManager.releaseChannels(activity) }
|
||||
.getOrElse { DeckyManager.ReleaseChannels(emptyList(), emptyList()) }
|
||||
ui.post {
|
||||
if (request == deckyReleaseRequest) {
|
||||
deckyChannels = channels
|
||||
deckyLatest = DeckyManager.latest(channels, deckyBranch)
|
||||
deckyReleases = channels.stable.ifEmpty { channels.prerelease }
|
||||
deckyChecking = false
|
||||
}
|
||||
}
|
||||
}, "decky-releases").start()
|
||||
}
|
||||
|
||||
fun switchDeckyBranch(branch: Int) {
|
||||
if (branch == deckyBranch || deckyStage != null || SessionState.running) return
|
||||
val saved = runCatching { DeckyManager.setBranch(activity, branch) }
|
||||
if (saved.isFailure) {
|
||||
android.widget.Toast.makeText(activity, R.string.decky_channel_failed, android.widget.Toast.LENGTH_LONG).show()
|
||||
return
|
||||
}
|
||||
deckyBranch = branch
|
||||
deckyLatest = DeckyManager.latest(deckyChannels, branch)
|
||||
refreshDecky()
|
||||
}
|
||||
|
||||
fun installDecky(release: DeckyManager.Release) {
|
||||
if (deckyStage != null || SessionState.running) return
|
||||
deckyStage = activity.getString(R.string.store_starting); deckyPercent = -1
|
||||
@@ -66,17 +49,6 @@ internal class DeckyMenu(private val activity: android.app.Activity, private val
|
||||
}
|
||||
|
||||
fun importPluginZip(archive: File) {
|
||||
if (deckyStage != null || SessionState.running || deckyInstalled == null) return
|
||||
deckyPendingZip = archive
|
||||
}
|
||||
|
||||
fun cancelPluginZip() {
|
||||
deckyPendingZip = null
|
||||
}
|
||||
|
||||
fun confirmPluginZip() {
|
||||
val archive = deckyPendingZip ?: return
|
||||
deckyPendingZip = null
|
||||
if (deckyStage != null || SessionState.running || deckyInstalled == null) return
|
||||
deckyStage = activity.getString(R.string.decky_plugin_starting); deckyPercent = -1
|
||||
Thread({
|
||||
|
||||
@@ -160,7 +160,7 @@ class MainActivity : ComponentActivity() {
|
||||
private var directAudio by mutableStateOf(false)
|
||||
private var picturePosition by mutableStateOf("center")
|
||||
private var stretch16x9 by mutableStateOf(false)
|
||||
private var orientation by mutableStateOf(android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
|
||||
private var orientation by mutableStateOf(android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE)
|
||||
private var launcherFullscreen by mutableStateOf(true)
|
||||
private var downloadSource by mutableStateOf(DownloadSource.MODE_AUTO)
|
||||
private var downloadMirror by mutableStateOf("")
|
||||
@@ -768,9 +768,6 @@ class MainActivity : ComponentActivity() {
|
||||
setContent {
|
||||
DroidDeckTheme(theme, appScale = appScale) {
|
||||
com.droiddeck.launcher.ui.FocusGlideHost(androidx.compose.ui.Modifier.fillMaxSize().exposeTestTags()) {
|
||||
if (decky.deckyPendingZip != null) com.droiddeck.launcher.ui.DeckyZipWarningDialog(
|
||||
onConfirm = { decky.confirmPluginZip() }, onCancel = { decky.cancelPluginZip() },
|
||||
)
|
||||
val page: (@Composable () -> Unit)? = when {
|
||||
showPerformance -> { { PerformanceHost() } }
|
||||
showComponents -> { { ComponentsHost() } }
|
||||
@@ -1649,8 +1646,7 @@ class MainActivity : ComponentActivity() {
|
||||
wifiDiscoveryAsked = wifiDiscoveryAsked,
|
||||
wifiDiscoveryBlocked = wifiDiscoveryBlocked,
|
||||
deckyInstalled = if (mode == SessionService.MODE_STEAM) decky.deckyInstalled else null,
|
||||
deckyLatestRelease = if (mode == SessionService.MODE_STEAM) decky.deckyLatest else null,
|
||||
deckyBranch = decky.deckyBranch,
|
||||
deckyLatestRelease = if (mode == SessionService.MODE_STEAM) decky.deckyReleases.firstOrNull() else null,
|
||||
deckyChecking = decky.deckyChecking, deckyStage = decky.deckyStage, deckyPercent = decky.deckyPercent,
|
||||
deckyDownloadingBinary = decky.deckyDownloadingBinary,
|
||||
deckyEnabled = decky.deckySupervisor, deckySessionRunning = SessionState.running,
|
||||
@@ -1733,7 +1729,6 @@ class MainActivity : ComponentActivity() {
|
||||
onBootMovieChanged = { loadBootMovie() },
|
||||
onDeckyInstall = { release -> decky.installDecky(release) },
|
||||
onDeckyCheck = { decky.refreshDecky() },
|
||||
onDeckyBranch = { branch -> decky.switchDeckyBranch(branch) },
|
||||
onDeckyEnabled = { enabled -> DeckyManager.setSupervisorEnabled(this, enabled); decky.deckySupervisor = enabled },
|
||||
onDeckyUninstall = {
|
||||
DeckyManager.uninstall(this, wipeData = false)
|
||||
|
||||
@@ -29,7 +29,6 @@ import androidx.activity.ComponentActivity
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -112,7 +111,7 @@ import com.droiddeck.launcher.ui.HudText
|
||||
import com.droiddeck.launcher.ui.LoadingOverlay
|
||||
import com.droiddeck.launcher.ui.SessionDrawer
|
||||
import com.droiddeck.launcher.ui.SessionPausedOverlay
|
||||
import com.droiddeck.launcher.ui.SecondScreenPresentation
|
||||
import com.droiddeck.launcher.ui.SecondScreenWindow
|
||||
import com.droiddeck.launcher.ui.DroidDeckTheme
|
||||
import com.droiddeck.launcher.wayland.CompositorHost
|
||||
import com.droiddeck.launcher.wayland.WaylandCompositor
|
||||
@@ -307,11 +306,6 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
private var achievementsInFront by mutableStateOf<AchievementsInFront?>(null)
|
||||
private var achievementsOpen by mutableStateOf(false)
|
||||
private var hudOn by mutableStateOf(true)
|
||||
private var hudFps by mutableStateOf(true)
|
||||
private var hudBattery by mutableStateOf(true)
|
||||
private var hudGraph by mutableStateOf(true)
|
||||
private var hudColor by mutableStateOf(SessionPrefs.HUD_COLOR_MINT)
|
||||
private var hudOpacity by mutableStateOf(SessionPrefs.HUD_OPACITY_DEFAULT)
|
||||
private var upscaler by mutableStateOf(0)
|
||||
private var upscaleSharpness by mutableStateOf(75)
|
||||
private var effects by mutableStateOf(ScreenEffects.OFF)
|
||||
@@ -367,7 +361,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
private var selectedSecondScreenDisplay by mutableStateOf(SessionState.secondScreenDisplay)
|
||||
private var started = false
|
||||
private lateinit var displayManager: DisplayManager
|
||||
private var secondScreenPresentation: SecondScreenPresentation? = null
|
||||
private var secondScreenPresentation: SecondScreenWindow? = null
|
||||
private val displayListener = object : DisplayManager.DisplayListener {
|
||||
override fun onDisplayAdded(displayId: Int) = refreshSecondScreenDisplays()
|
||||
override fun onDisplayRemoved(displayId: Int) = refreshSecondScreenDisplays()
|
||||
@@ -475,7 +469,6 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
|
||||
val bridge = PadBridge(File(LinuxRuntime.sessionRoot(this), "dev/input"))
|
||||
bridge.setSelectSteam(ControllerPrefs.read(this).selectSteam)
|
||||
bridge.setBuiltinPad(SessionState.mode != SessionService.MODE_STEAM || SessionPrefs.builtinPad(this))
|
||||
padBridge = bridge
|
||||
SessionState.padBridge = bridge
|
||||
if (Build.VERSION.SDK_INT >= 31) {
|
||||
@@ -645,9 +638,8 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
com.droiddeck.launcher.ui.FocusGlideHost(androidx.compose.ui.Modifier.fillMaxSize()) {
|
||||
CursorOverlay({ cursorPos }, cursorVisible, resources.displayMetrics.density,
|
||||
cursorImage, cursorHotX, cursorHotY, cursorImageScale)
|
||||
if (hud.text.isNotEmpty() || hud.graph.size >= 2) HudText(hud.text, hud.graph, hudColor, hudOpacity)
|
||||
val phonePanel = glassesOutputActive && !loading.visible && !drawerOpen && !pcKeyboardOpen
|
||||
if (phonePanel) GlassesPhonePanel(glasses, steamVrStatus == SteamVr.Status.RUNNING, headTracking)
|
||||
if (hud.text.isNotEmpty()) HudText(hud.text)
|
||||
if (glassesOutputActive && !loading.visible) com.droiddeck.launcher.ui.GlassesPlayingHint(glasses.model)
|
||||
if (trackingRecovery != null && !drawerOpen && !pcKeyboardOpen && !loading.visible) {
|
||||
androidx.compose.ui.viewinterop.AndroidView(
|
||||
factory = { com.droiddeck.launcher.ui.TrackingRecoveryView(it) },
|
||||
@@ -655,14 +647,14 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
update = { it.update(trackingRecovery, !glassesOutputActive && glasses.usable && glasses.sbs) },
|
||||
)
|
||||
}
|
||||
if (!phonePanel && GlassesRendering.usesGlasses(glasses, glassesOutputActive, steamVrStatus == SteamVr.Status.RUNNING) &&
|
||||
if (GlassesRendering.usesGlasses(glasses, glassesOutputActive, steamVrStatus == SteamVr.Status.RUNNING) &&
|
||||
headTracking && !drawerOpen && !pcKeyboardOpen && !loading.visible) com.droiddeck.launcher.ui.GlassesRecenterButton(
|
||||
controllerSettings?.tint ?: android.graphics.Color.rgb(90, 160, 255), controllerSettings?.opacity ?: 100,
|
||||
onRecenterJoyCons = if (steamVrStatus == SteamVr.Status.RUNNING &&
|
||||
glasses.usesSpatialControllers(glassesSettings) && glassesSettings.joyConTracking)
|
||||
joyConVr?.let { { it.recenter() } } else null,
|
||||
) { headTracker?.recenter() }
|
||||
if (!phonePanel && glasses.present && !drawerOpen && !pcKeyboardOpen && !loading.visible) com.droiddeck.launcher.ui.GlassesCornerButton(glassesSlot,
|
||||
if (glasses.present && !drawerOpen && !pcKeyboardOpen && !loading.visible) com.droiddeck.launcher.ui.GlassesCornerButton(glassesSlot,
|
||||
controllerSettings?.tint ?: android.graphics.Color.rgb(90, 160, 255), controllerSettings?.opacity ?: 100) {
|
||||
drawerPage = com.droiddeck.launcher.ui.SessionDrawerPage.GLASSES
|
||||
drawerOpen = true
|
||||
@@ -760,11 +752,6 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
isHomeApp = isHomeApp,
|
||||
androidApps = androidApps,
|
||||
hudOn = hudOn,
|
||||
hudFps = hudFps,
|
||||
hudBattery = hudBattery,
|
||||
hudGraph = hudGraph,
|
||||
hudColor = hudColor,
|
||||
hudOpacity = hudOpacity,
|
||||
upscaler = upscaler, upscaleSharpness = upscaleSharpness,
|
||||
effects = effects,
|
||||
cardboard = cardboard,
|
||||
@@ -792,11 +779,6 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
secondScreenDisplays = secondScreenDisplays,
|
||||
selectedSecondScreenDisplay = selectedSecondScreenDisplay,
|
||||
onHud = { on -> SessionPrefs.setHudEnabled(this@SessionActivity, on); hudOn = on; hud.refresh() },
|
||||
onHudFps = { on -> SessionPrefs.setHudShowFps(this@SessionActivity, on); hudFps = on; hud.refresh() },
|
||||
onHudBattery = { on -> SessionPrefs.setHudShowBattery(this@SessionActivity, on); hudBattery = on; hud.refresh() },
|
||||
onHudGraph = { on -> SessionPrefs.setHudShowGraph(this@SessionActivity, on); hudGraph = on; hud.refresh() },
|
||||
onHudColor = { color -> SessionPrefs.setHudColor(this@SessionActivity, color); hudColor = color },
|
||||
onHudOpacity = { percent -> SessionPrefs.setHudOpacity(this@SessionActivity, percent); hudOpacity = percent },
|
||||
onUpscaler = { m ->
|
||||
SessionPrefs.setUpscaler(this@SessionActivity, m); upscaler = m
|
||||
WaylandCompositor.nativeSetUpscaler(m)
|
||||
@@ -1016,54 +998,6 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
}, 90)
|
||||
}
|
||||
|
||||
@androidx.compose.runtime.Composable
|
||||
private fun GlassesPhonePanel(glasses: com.droiddeck.launcher.glasses.GlassesState, vr: Boolean, headTracking: Boolean) {
|
||||
val battery by androidx.compose.runtime.produceState<String?>(null) {
|
||||
while (true) {
|
||||
val b = registerReceiver(null, android.content.IntentFilter(android.content.Intent.ACTION_BATTERY_CHANGED))
|
||||
val level = b?.getIntExtra(android.os.BatteryManager.EXTRA_LEVEL, -1) ?: -1
|
||||
val scale = b?.getIntExtra(android.os.BatteryManager.EXTRA_SCALE, 100) ?: 100
|
||||
val tenths = b?.getIntExtra(android.os.BatteryManager.EXTRA_TEMPERATURE, -1) ?: -1
|
||||
value = if (level < 0) null else getString(R.string.glasses_phone_battery, level * 100 / scale.coerceAtLeast(1), tenths / 10)
|
||||
kotlinx.coroutines.delay(30_000)
|
||||
}
|
||||
}
|
||||
val pads = com.droiddeck.launcher.input.VrControllers.settings.collectAsState().value
|
||||
com.droiddeck.launcher.ui.GlassesPhonePanel(glasses.model, battery, com.droiddeck.launcher.ui.GlassesPhoneActions(
|
||||
onRecenter = if (headTracking) ({ headTracker?.recenter() }) else null,
|
||||
onRecenterJoyCons = if (vr && glasses.usesSpatialControllers(glassesSettings) && glassesSettings.joyConTracking)
|
||||
joyConVr?.let { { it.recenter() } } else null,
|
||||
onSteamVrMenu = if (vr && SessionState.mode == SessionService.MODE_STEAM) ({ sendSteamGuide() }) else null,
|
||||
onSwitchSticks = if (vr && pads.emulate != com.droiddeck.launcher.input.VrControllers.Emulate.OFF) ({ switchVrSticks() }) else null,
|
||||
controllers = getString(when (pads.emulate) {
|
||||
com.droiddeck.launcher.input.VrControllers.Emulate.OFF -> R.string.vr_controllers_off
|
||||
com.droiddeck.launcher.input.VrControllers.Emulate.INDEX -> R.string.vr_controllers_index
|
||||
com.droiddeck.launcher.input.VrControllers.Emulate.TOUCH -> R.string.vr_controllers_touch
|
||||
com.droiddeck.launcher.input.VrControllers.Emulate.VIVE -> R.string.vr_controllers_vive
|
||||
}),
|
||||
onNextControllers = if (vr) ({
|
||||
val all = com.droiddeck.launcher.input.VrControllers.Emulate.entries
|
||||
com.droiddeck.launcher.input.VrControllers.setEmulate(this, all[(pads.emulate.ordinal + 1) % all.size])
|
||||
}) else null,
|
||||
onKeyboard = { keyboard?.toggle() },
|
||||
onSessionMenu = { drawerOpen = true },
|
||||
onStopSteamVr = if (vr) ({ steamVrAction("stop") }) else null,
|
||||
brightness = glasses.brightness,
|
||||
brightnessMax = glasses.brightnessMax,
|
||||
onBrightness = if (glasses.canControl && glasses.hardwareControls) Glasses::setBrightness else null,
|
||||
))
|
||||
}
|
||||
|
||||
/** The SteamVR driver switches pad sticks between hands and game on both stick clicks together. */
|
||||
private fun switchVrSticks() {
|
||||
val chord = { down: Boolean -> padBridge?.applyTouch { state ->
|
||||
state.press(com.droiddeck.launcher.input.PadState.L3, down)
|
||||
state.press(com.droiddeck.launcher.input.PadState.R3, down)
|
||||
} }
|
||||
chord(true)
|
||||
uiHandler.postDelayed({ chord(false) }, 120)
|
||||
}
|
||||
|
||||
private fun triggerSteamQam() {
|
||||
drawerOpen = false
|
||||
padBridge?.triggerQam()
|
||||
@@ -1409,11 +1343,6 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
unfoldedControls = SessionPrefs.unfoldedControls(this)
|
||||
picturePosition = SessionPrefs.picturePosition(this)
|
||||
hudOn = SessionPrefs.hudEnabled(this)
|
||||
hudFps = SessionPrefs.hudShowFps(this)
|
||||
hudBattery = SessionPrefs.hudShowBattery(this)
|
||||
hudGraph = SessionPrefs.hudShowGraph(this)
|
||||
hudColor = SessionPrefs.hudColor(this)
|
||||
hudOpacity = SessionPrefs.hudOpacity(this)
|
||||
upscaler = SessionPrefs.upscaler(this)
|
||||
upscaleSharpness = SessionPrefs.upscaleSharpness(this)
|
||||
effects = SessionPrefs.screenEffects(this)
|
||||
@@ -1596,7 +1525,6 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
}
|
||||
val runtimeDir = File(filesDir, ".wayland-rt").apply { mkdirs() }
|
||||
FileUtils.copyAsset(this, "wayland/keymap.xkb", File(runtimeDir, "keymap.xkb"))
|
||||
com.droiddeck.launcher.input.VrControllers.publish(this)
|
||||
|
||||
val turnip = TurnipDriver(this)
|
||||
com.droiddeck.launcher.gpu.DriverSetupLog.write(this, "surface.driver-setup")
|
||||
@@ -2408,7 +2336,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
var presentation = secondScreenPresentation
|
||||
if (presentation?.display?.displayId != target.displayId) {
|
||||
presentation?.closeControls()
|
||||
presentation = SecondScreenPresentation(
|
||||
presentation = SecondScreenWindow(
|
||||
this, target,
|
||||
sendPointer = ::moveSecondScreenPointer,
|
||||
sendButton = { button, pressed ->
|
||||
@@ -2748,8 +2676,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
?.let { FileUtils.readString(it)?.trim()?.lowercase() }
|
||||
?: SessionPrefs.oscMode(this)
|
||||
val controls = onScreenControls ?: return
|
||||
if (pipUi || unfoldedPanel != null || pcKeyboardOpen ||
|
||||
(SessionState.mode == SessionService.MODE_STEAM && !SessionPrefs.builtinPad(this))) {
|
||||
if (pipUi || unfoldedPanel != null || pcKeyboardOpen) {
|
||||
onScreenButtonsVisible = false
|
||||
controls.releaseAll()
|
||||
controls.visibility = View.GONE
|
||||
|
||||
@@ -19,10 +19,7 @@ import java.io.FileInputStream
|
||||
* yet to its pr-N release on DroidDeck-CI. The APK pins the pack's SHA-256, so only the bytes this build
|
||||
* was made with install, whichever host serves them.
|
||||
*/
|
||||
internal class AddonPack(
|
||||
val name: String, private val sha256: String, val size: Long, private val required: List<String>,
|
||||
private val reuseDeviceCopy: Boolean = sha256.isEmpty() && !BuildConfig.CI_BUILD,
|
||||
) {
|
||||
internal class AddonPack(val name: String, private val sha256: String, val size: Long, private val required: List<String>) {
|
||||
sealed interface State {
|
||||
data object Missing : State
|
||||
data class Downloading(val progress: Float) : State
|
||||
@@ -43,20 +40,10 @@ internal class AddonPack(
|
||||
|
||||
private fun root(context: Context) = File(context.filesDir, "addons/$name")
|
||||
/** App-private and written only after the archive matched the pinned digest. */
|
||||
fun dir(context: Context) = File(root(context), version(context))
|
||||
fun dir(context: Context) = File(root(context), sha256)
|
||||
fun file(context: Context, relative: String) = File(dir(context), relative)
|
||||
|
||||
fun installed(context: Context): Boolean = version(context).isNotEmpty() && File(dir(context), COMPLETE).isFile
|
||||
|
||||
// A build made outside CI pins no packs, so it runs the newest one a CI build already verified and installed.
|
||||
private fun version(context: Context): String = if (!reuseDeviceCopy) sha256 else deviceCopy(context).orEmpty()
|
||||
|
||||
private fun deviceCopy(context: Context): String? = root(context).listFiles().orEmpty()
|
||||
.filter { dir ->
|
||||
dir.isDirectory && runCatching { File(dir, COMPLETE).readText().trim() }.getOrNull() == dir.name &&
|
||||
required.all { File(dir, it).isFile }
|
||||
}
|
||||
.maxByOrNull { it.lastModified() }?.name
|
||||
fun installed(context: Context): Boolean = available && File(dir(context), COMPLETE).isFile
|
||||
|
||||
fun refresh(context: Context): State {
|
||||
val now = mutableState.value
|
||||
@@ -99,7 +86,7 @@ internal class AddonPack(
|
||||
|
||||
private fun install(context: Context): Boolean {
|
||||
if (!available) {
|
||||
Log.w(TAG, if (reuseDeviceCopy) "$name: this build has no pack and none is installed" else "$name: this build has no pack")
|
||||
Log.w(TAG, "$name: this build has no pack")
|
||||
return false
|
||||
}
|
||||
val root = root(context).apply { mkdirs() }
|
||||
|
||||
@@ -50,7 +50,6 @@ data class GlassesState(
|
||||
val noSignal: Boolean = false,
|
||||
val sbsSupport: Int = SBS_UNKNOWN,
|
||||
val sbs90: Boolean = false,
|
||||
val rates2d: List<Int> = listOf(60, 90, 120),
|
||||
val experimental: Boolean = false,
|
||||
val supportsScreen3dof: Boolean = false,
|
||||
val supports30Hz: Boolean = false,
|
||||
@@ -614,13 +613,12 @@ object Glasses {
|
||||
switching3d = modeState.pendingStereo,
|
||||
brightnessMax = m?.brightnessMax ?: 8, volumeMax = m?.volumeMax ?: 8,
|
||||
sunblock = if (m === XrealModel) it.sunblock else _settings.value.sunblock ?: it.sunblock,
|
||||
hardwareControls = device?.vendorId == VitureProtocol.VENDOR_ID || m === XrealModel || m is RokidModel,
|
||||
hardwareControls = device?.vendorId == VitureProtocol.VENDOR_ID || m === XrealModel,
|
||||
hasSunblock = m?.hasSunblock == true,
|
||||
diagonalFovDeg = m?.diagonalFovDeg ?: 46f,
|
||||
tracking = it.tracking && link != null,
|
||||
noSignal = noSignal,
|
||||
sbs90 = m?.sbs90 == true,
|
||||
rates2d = m?.rates2d ?: listOf(60, 90, 120),
|
||||
experimental = m === XrealModel,
|
||||
supportsScreen3dof = link?.hasImu == true,
|
||||
supports30Hz = display?.let(::supports30Hz) == true,
|
||||
@@ -636,8 +634,7 @@ object Glasses {
|
||||
scheduleModeCheck()
|
||||
}
|
||||
|
||||
internal fun isGlassesVendor(vid: Int) = vid == VitureProtocol.VENDOR_ID || RayNeoModel.isVendor(vid) ||
|
||||
vid == XrealModel.VENDOR_ID || vid == RokidModel.VENDOR_ID
|
||||
internal fun isGlassesVendor(vid: Int) = vid == VitureProtocol.VENDOR_ID || RayNeoModel.isVendor(vid) || vid == XrealModel.VENDOR_ID
|
||||
|
||||
internal fun supports30Hz(display: Display): Boolean {
|
||||
val current = display.mode
|
||||
@@ -657,13 +654,11 @@ object Glasses {
|
||||
?: VitureProtocol.model(device.productId, name)
|
||||
}
|
||||
else if (XrealModel.matches(device.vendorId, device.productId)) XrealModel
|
||||
else RokidModel.model(device.vendorId, device.productId, name)
|
||||
?: RayNeoModel.model(device.vendorId, device.productId, name)
|
||||
else RayNeoModel.model(device.vendorId, device.productId, name)
|
||||
}
|
||||
|
||||
internal fun isGlassesDisplayName(name: String) =
|
||||
name.contains("VITURE", ignoreCase = true) || RayNeoModel.isDisplay(name) || XrealModel.isDisplay(name) ||
|
||||
RokidModel.isDisplay(name)
|
||||
name.contains("VITURE", ignoreCase = true) || RayNeoModel.isDisplay(name) || XrealModel.isDisplay(name)
|
||||
|
||||
private fun findDisplay(usbPresent: Boolean): Display? {
|
||||
return selectGlassesDisplay(displays.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION).toList(), usbPresent)
|
||||
@@ -728,8 +723,6 @@ object Glasses {
|
||||
} else VitureLink.open(usb, device, linkListener(generation))
|
||||
} else if (model === XrealModel) {
|
||||
XrealLink.open(app, xrealListener(generation))
|
||||
} else if (model is RokidModel) {
|
||||
RokidLink.open(usb, device, rokidListener(generation))
|
||||
} else {
|
||||
val baseKey = calibrationKey!!
|
||||
val gtUsb = RayNeoProtocol.isGt(device.vendorId, device.productId)
|
||||
@@ -797,33 +790,6 @@ object Glasses {
|
||||
}
|
||||
}
|
||||
|
||||
private fun rokidListener(generation: Long) = object : RokidLink.Listener {
|
||||
override fun onReady() = post {
|
||||
if (generation != linkGeneration) return@post
|
||||
publish { it.copy(connected = true, controlError = null) }
|
||||
updateImu()
|
||||
applyMode(force = false)
|
||||
}
|
||||
override fun onValue(message: Int, value: Int) = post {
|
||||
if (generation != linkGeneration) return@post
|
||||
when (message) {
|
||||
VitureProtocol.MSG_GET_DISPLAY -> glassesMode = value
|
||||
VitureProtocol.MSG_BRIGHTNESS -> publish { it.copy(brightness = value) }
|
||||
}
|
||||
}
|
||||
override fun onPose(raw: ByteArray, pose: FloatArray, timeNs: Long) {
|
||||
if (generation != linkGeneration) return
|
||||
imuPackets++
|
||||
probe?.invoke(raw, pose)
|
||||
if (simulating.get() == null) deliver(pose, timeNs)
|
||||
}
|
||||
override fun onLost() = post {
|
||||
if (generation != linkGeneration) return@post
|
||||
closeLink()
|
||||
refresh()
|
||||
}
|
||||
}
|
||||
|
||||
private fun sdkListener(generation: Long) = object : VitureSdkLink.Listener {
|
||||
override fun onSpatial(pose: FloatArray, timeNs: Long, valid: Boolean) {
|
||||
if (generation == linkGeneration) SpatialTracking.accept(pose, timeNs, generation, valid)
|
||||
|
||||
@@ -8,7 +8,6 @@ interface GlassesModel {
|
||||
val volumeMax: Int
|
||||
val diagonalFovDeg: Float
|
||||
val sbs90: Boolean
|
||||
val rates2d: List<Int> get() = listOf(60, 90, 120)
|
||||
val hasSunblock: Boolean get() = true
|
||||
val resolutions: List<GlassesResolution> get() = emptyList()
|
||||
fun mode(hz: Int, sbs: Boolean, resolution: GlassesResolution?): Int = mode(hz, sbs)
|
||||
|
||||
@@ -12,8 +12,8 @@ internal object GlassesRendering {
|
||||
fun refreshRates(settings: GlassesSettings, state: GlassesState): List<Int> = buildList {
|
||||
if (native30HzAvailable(settings, state)) add(30)
|
||||
add(60)
|
||||
if (state.sbs) { if (state.sbs90) add(90) }
|
||||
else addAll(state.rates2d.filter { it > 60 })
|
||||
if (!state.sbs || state.sbs90) add(90)
|
||||
if (!state.sbs) add(120)
|
||||
}
|
||||
|
||||
fun outputHz(settings: GlassesSettings, state: GlassesState): Int {
|
||||
@@ -21,7 +21,6 @@ internal object GlassesRendering {
|
||||
return when {
|
||||
requested == 30 -> if (native30HzAvailable(settings, state)) 30 else 60
|
||||
state.sbs && !state.sbs90 -> 60
|
||||
!state.sbs && requested !in state.rates2d -> state.rates2d.filter { it <= requested }.maxOrNull() ?: 60
|
||||
else -> requested
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
package com.droiddeck.launcher.glasses
|
||||
|
||||
import android.hardware.usb.UsbConstants
|
||||
import android.hardware.usb.UsbDevice
|
||||
import android.hardware.usb.UsbDeviceConnection
|
||||
import android.hardware.usb.UsbEndpoint
|
||||
import android.hardware.usb.UsbInterface
|
||||
import android.hardware.usb.UsbManager
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
|
||||
/** Controls are vendor control transfers; the HID interrupt endpoint streams IMU reports unprompted. */
|
||||
internal class RokidLink private constructor(
|
||||
private val connection: UsbDeviceConnection,
|
||||
private val usbInterface: UsbInterface,
|
||||
private val endpoint: UsbEndpoint,
|
||||
private val listener: Listener,
|
||||
) : GlassesLink {
|
||||
interface Listener {
|
||||
fun onReady()
|
||||
fun onValue(message: Int, value: Int)
|
||||
fun onPose(raw: ByteArray, pose: FloatArray, timeNs: Long)
|
||||
fun onLost()
|
||||
}
|
||||
|
||||
@Volatile private var running = true
|
||||
@Volatile override var ready = false
|
||||
private set
|
||||
@Volatile private var tracking = false
|
||||
private val controlLock = Any()
|
||||
private lateinit var worker: Thread
|
||||
override val hasImu = true
|
||||
|
||||
override fun send(msgId: Int, payload: ByteArray): Boolean {
|
||||
if (!running) return false
|
||||
val value = payload.firstOrNull()?.toInt()?.and(0xff) ?: -1
|
||||
return when (msgId) {
|
||||
VitureProtocol.MSG_IMU -> (value in 0..1).also { if (it) tracking = value == 1 }
|
||||
VitureProtocol.MSG_GET_DISPLAY -> display()?.let { listener.onValue(msgId, it) } != null
|
||||
VitureProtocol.MSG_DISPLAY -> value in RokidModel.MODES &&
|
||||
control(RokidModel.REQUEST_SET_DISPLAY, value, RokidModel.INDEX_DISPLAY) &&
|
||||
true.also { listener.onValue(VitureProtocol.MSG_GET_DISPLAY, value) }
|
||||
VitureProtocol.MSG_GET_BRIGHTNESS -> read(RokidModel.REQUEST_GET_BRIGHTNESS, RokidModel.INDEX_BRIGHTNESS)
|
||||
?.let { listener.onValue(VitureProtocol.MSG_BRIGHTNESS, RokidModel.brightnessStep(it)) } != null
|
||||
VitureProtocol.MSG_BRIGHTNESS -> value in 0..RokidModel.BRIGHTNESS_STEPS &&
|
||||
control(RokidModel.REQUEST_SET_BRIGHTNESS, RokidModel.brightnessRaw(value), RokidModel.INDEX_BRIGHTNESS)
|
||||
else -> false // Volume is the glasses' USB audio device; there is no film or IMU-rate control.
|
||||
}
|
||||
}
|
||||
|
||||
private fun display(): Int? = read(RokidModel.REQUEST_GET_DISPLAY, RokidModel.INDEX_DISPLAY)
|
||||
|
||||
private fun read(request: Int, index: Int): Int? = synchronized(controlLock) {
|
||||
if (!running) return null
|
||||
val buffer = ByteArray(64)
|
||||
val count = connection.controlTransfer(VENDOR_IN, request, 0, index, buffer, buffer.size, TIMEOUT_MS)
|
||||
// Display mode is reported in the second byte, brightness in the first.
|
||||
val offset = if (request == RokidModel.REQUEST_GET_DISPLAY) 1 else 0
|
||||
if (count > offset) buffer[offset].toInt() and 0xff else null
|
||||
}
|
||||
|
||||
private fun control(request: Int, value: Int, index: Int): Boolean = synchronized(controlLock) {
|
||||
running && connection.controlTransfer(VENDOR_OUT, request, value, index, byteArrayOf(1), 1, TIMEOUT_MS) >= 0
|
||||
}
|
||||
|
||||
private fun run() {
|
||||
val buffer = ByteArray(maxOf(endpoint.maxPacketSize, 64))
|
||||
val parser = RokidProtocol.Parser()
|
||||
var fusion = ImuFusion()
|
||||
var wasTracking = false
|
||||
var brightness = -1
|
||||
var lastSensor = SystemClock.elapsedRealtime()
|
||||
try {
|
||||
ready = true
|
||||
listener.onReady()
|
||||
while (running) {
|
||||
val count = connection.bulkTransfer(endpoint, buffer, buffer.size, 250)
|
||||
val now = SystemClock.elapsedRealtime()
|
||||
val sample = if (count > 0) parser.parse(buffer, count) else null
|
||||
if (sample == null) {
|
||||
if (now - lastSensor > 3000) error("Rokid IMU stream stalled")
|
||||
continue
|
||||
}
|
||||
lastSensor = now
|
||||
sample.brightness?.takeIf { it != brightness }?.let {
|
||||
brightness = it
|
||||
listener.onValue(VitureProtocol.MSG_BRIGHTNESS, RokidModel.brightnessStep(it))
|
||||
}
|
||||
val enabled = tracking
|
||||
if (enabled && !wasTracking) fusion = ImuFusion()
|
||||
wasTracking = enabled
|
||||
if (!enabled) continue
|
||||
// Max captures: already right/up/back; level gravity +Y, turn left +gyroY, look up +gyroX, roll right -gyroZ.
|
||||
fusion.update(sample.timeNs, sample.accel, sample.gyro)?.let {
|
||||
if (running && tracking) listener.onPose(buffer.copyOf(count), it, SystemClock.elapsedRealtimeNanos())
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (running) Log.w(TAG, "Rokid stream failed", e)
|
||||
} finally {
|
||||
ready = false
|
||||
if (running) listener.onLost()
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
synchronized(controlLock) {
|
||||
if (!running) return
|
||||
running = false
|
||||
}
|
||||
if (Thread.currentThread() !== worker) worker.join(500)
|
||||
runCatching { connection.releaseInterface(usbInterface) }
|
||||
connection.close()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "RokidLink"
|
||||
private const val TIMEOUT_MS = 250
|
||||
private const val VENDOR_IN = UsbConstants.USB_DIR_IN or UsbConstants.USB_TYPE_VENDOR
|
||||
private const val VENDOR_OUT = UsbConstants.USB_DIR_OUT or UsbConstants.USB_TYPE_VENDOR
|
||||
|
||||
internal fun sensorEndpoint(device: UsbDevice): Pair<UsbInterface, UsbEndpoint>? {
|
||||
for (i in 0 until device.interfaceCount) {
|
||||
val iface = device.getInterface(i)
|
||||
if (iface.interfaceClass != UsbConstants.USB_CLASS_HID) continue
|
||||
for (e in 0 until iface.endpointCount) {
|
||||
val endpoint = iface.getEndpoint(e)
|
||||
if (endpoint.direction == UsbConstants.USB_DIR_IN &&
|
||||
endpoint.type == UsbConstants.USB_ENDPOINT_XFER_INT) return iface to endpoint
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun open(usb: UsbManager, device: UsbDevice, listener: Listener): RokidLink? {
|
||||
val (iface, endpoint) = sensorEndpoint(device) ?: return null
|
||||
val conn = usb.openDevice(device) ?: return null
|
||||
if (!conn.claimInterface(iface, true)) {
|
||||
conn.close()
|
||||
return null
|
||||
}
|
||||
return RokidLink(conn, iface, endpoint, listener).also { link ->
|
||||
link.worker = Thread({ link.run() }, "rokid-imu").apply { isDaemon = true }
|
||||
link.worker.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
package com.droiddeck.launcher.glasses
|
||||
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
|
||||
/** Rokid Air/Max share one USB identity; the product string distinguishes them. */
|
||||
internal class RokidModel private constructor(override val name: String, private val max: Boolean) : GlassesModel {
|
||||
override val controllable = true
|
||||
override val brightnessMax = BRIGHTNESS_STEPS
|
||||
override val volumeMax = 0
|
||||
override val hasSunblock = false
|
||||
override val diagonalFovDeg = if (max) 50f else 43f
|
||||
override val sbs90 = max
|
||||
override val rates2d = if (max) listOf(60, 120) else listOf(60)
|
||||
override val resolutions = if (max) listOf(GlassesResolution(1920, 1080), GlassesResolution(1920, 1200))
|
||||
else listOf(GlassesResolution(1920, 1080))
|
||||
|
||||
override fun mode(hz: Int, sbs: Boolean): Int = mode(hz, sbs, null)
|
||||
|
||||
override fun mode(hz: Int, sbs: Boolean, resolution: GlassesResolution?): Int = when {
|
||||
!max -> if (sbs) MODE_SBS_1080_60 else MODE_2D_60
|
||||
sbs && resolution?.height == 1080 -> MODE_SBS_1080_60
|
||||
sbs -> if (hz >= 90) MODE_SBS_1200_90 else MODE_SBS_1200_60
|
||||
hz >= 120 -> MODE_2D_120
|
||||
else -> MODE_2D_60
|
||||
}
|
||||
|
||||
override fun modeForStep(step: Int): Int? = null
|
||||
|
||||
override fun equals(other: Any?) = other is RokidModel && other.name == name && other.max == max
|
||||
override fun hashCode() = name.hashCode()
|
||||
|
||||
companion object {
|
||||
const val VENDOR_ID = 0x04d2
|
||||
const val PRODUCT_ID = 0x162f
|
||||
const val BRIGHTNESS_STEPS = 10
|
||||
|
||||
// Vendor control requests, device recipient.
|
||||
const val REQUEST_SET_DISPLAY = 0x01
|
||||
const val REQUEST_GET_DISPLAY = 0x81
|
||||
const val REQUEST_SET_BRIGHTNESS = 0x02
|
||||
const val REQUEST_GET_BRIGHTNESS = 0x82
|
||||
const val INDEX_DISPLAY = 0x01
|
||||
const val INDEX_BRIGHTNESS = 0x02
|
||||
|
||||
const val MODE_2D_60 = 0
|
||||
const val MODE_SBS_1080_60 = 1
|
||||
const val MODE_2D_120 = 3
|
||||
const val MODE_SBS_1200_90 = 4
|
||||
const val MODE_SBS_1200_60 = 5
|
||||
val MODES = setOf(MODE_2D_60, MODE_SBS_1080_60, MODE_2D_120, MODE_SBS_1200_90, MODE_SBS_1200_60)
|
||||
|
||||
// 0x04d2 is a shared vendor ID, so the Rokid product string is required too.
|
||||
fun model(vid: Int, pid: Int, name: String?): GlassesModel? {
|
||||
if (vid != VENDOR_ID || pid != PRODUCT_ID) return null
|
||||
val product = name?.trim()?.takeIf { it.startsWith("Rokid", ignoreCase = true) } ?: return null
|
||||
return RokidModel(product, product.contains("Max", ignoreCase = true))
|
||||
}
|
||||
|
||||
fun isDisplay(name: String) = name.contains("Rokid", ignoreCase = true)
|
||||
|
||||
/** Device brightness is 0..100; the shared slider uses ten steps. */
|
||||
fun brightnessStep(raw: Int): Int = ((raw.coerceIn(0, 100) + 5) / 10).coerceIn(0, BRIGHTNESS_STEPS)
|
||||
fun brightnessRaw(step: Int): Int = step.coerceIn(0, BRIGHTNESS_STEPS) * 10
|
||||
}
|
||||
}
|
||||
|
||||
internal object RokidProtocol {
|
||||
data class Sample(val timeNs: Long, val accel: Vec3, val gyro: Vec3, val brightness: Int?)
|
||||
|
||||
private const val COMBINED = 17
|
||||
private const val SEPARATE = 4
|
||||
private const val SENSOR_ACCEL = 1
|
||||
private const val SENSOR_GYRO = 2
|
||||
|
||||
/** Max sends one combined report; Air sends accel and gyro reports that share a timestamp. */
|
||||
class Parser {
|
||||
private var accel: Vec3? = null
|
||||
private var accelTime = -1L
|
||||
private var gyro: Vec3? = null
|
||||
private var gyroTime = -2L
|
||||
|
||||
fun parse(packet: ByteArray, length: Int = packet.size): Sample? {
|
||||
if (length < 64) return null
|
||||
val b = ByteBuffer.wrap(packet, 0, length).order(ByteOrder.LITTLE_ENDIAN)
|
||||
return when (packet[0].toInt() and 0xff) {
|
||||
COMBINED -> Sample(b.getLong(1), vec(b, 9), vec(b, 21), packet[59].toInt() and 0xff)
|
||||
SEPARATE -> {
|
||||
val time = b.getLong(9) * 1000 // Air reports microseconds; Max reports nanoseconds.
|
||||
when (packet[1].toInt() and 0xff) {
|
||||
SENSOR_ACCEL -> { accel = vec(b, 21); accelTime = time }
|
||||
SENSOR_GYRO -> { gyro = vec(b, 21); gyroTime = time }
|
||||
else -> return null
|
||||
}
|
||||
if (accelTime != gyroTime) return null
|
||||
Sample(time, accel!!, gyro!!, null).also { accelTime = -1L; gyroTime = -2L }
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun vec(b: ByteBuffer, offset: Int) = Vec3(b.getFloat(offset), b.getFloat(offset + 4), b.getFloat(offset + 8))
|
||||
}
|
||||
}
|
||||
@@ -4,27 +4,20 @@ import kotlin.math.abs
|
||||
|
||||
/** 1S radians/sec and device nanoseconds, mapped using directed hardware captures. */
|
||||
internal class XrealOrientationFilter {
|
||||
private val fusion = ImuFusion()
|
||||
|
||||
// 1S reports right/down/forward. Rotate 180 degrees about X into the
|
||||
// shared right/up/back basis before either gravity alignment or fusion.
|
||||
// Captures: level gravity -Y; looking left -gyroY; up +gyroX; right tilt +gyroZ.
|
||||
fun update(sample: XrealProtocol.Sample): FloatArray? = fusion.update(sample.timeNs,
|
||||
Vec3(sample.accel.x, -sample.accel.y, -sample.accel.z),
|
||||
Vec3(sample.gyro.x, -sample.gyro.y, -sample.gyro.z))
|
||||
}
|
||||
|
||||
/** Gyro integration with gravity correction in the shared right/up/back basis, rad/s and device ns. */
|
||||
internal class ImuFusion {
|
||||
private var previous = 0L
|
||||
private var rotation = Quaternion.IDENTITY
|
||||
private var initialized = false
|
||||
private var bias = Vec3(0f, 0f, 0f)
|
||||
|
||||
fun update(timeNs: Long, accel: Vec3, gyro: Vec3): FloatArray? {
|
||||
if (timeNs <= previous) return null
|
||||
val dt = if (previous == 0L) 0f else (timeNs - previous) * 1e-9f
|
||||
previous = timeNs
|
||||
fun update(sample: XrealProtocol.Sample): FloatArray? {
|
||||
if (sample.timeNs <= previous) return null
|
||||
val dt = if (previous == 0L) 0f else (sample.timeNs - previous) * 1e-9f
|
||||
previous = sample.timeNs
|
||||
// 1S reports right/down/forward. Rotate 180 degrees about X into the
|
||||
// shared right/up/back basis before either gravity alignment or fusion.
|
||||
// Captures: level gravity -Y; looking left -gyroY; up +gyroX; right tilt +gyroZ.
|
||||
val accel = Vec3(sample.accel.x, -sample.accel.y, -sample.accel.z)
|
||||
val gyro = Vec3(sample.gyro.x, -sample.gyro.y, -sample.gyro.z)
|
||||
val norm = accel.length()
|
||||
val gravityValid = abs(norm - 9.81f) < 1.5f
|
||||
if (dt > 0.25f) initialized = false // Never integrate a reconnect/stall as motion.
|
||||
|
||||
@@ -55,7 +55,6 @@ public final class PadBridge {
|
||||
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
private boolean open;
|
||||
private volatile boolean selectSteam;
|
||||
private volatile boolean builtinPad = true;
|
||||
private TouchRoute touchRoute;
|
||||
private final PadState routedTouch = new PadState();
|
||||
private boolean systemGuidePressed;
|
||||
@@ -111,25 +110,6 @@ public final class PadBridge {
|
||||
excluded = devices;
|
||||
}
|
||||
|
||||
public synchronized void setBuiltinPad(boolean on) {
|
||||
boolean changed = builtinPad != on;
|
||||
builtinPad = on;
|
||||
if (changed && !on) resetBuiltin();
|
||||
}
|
||||
|
||||
private void resetBuiltin() {
|
||||
routedTouch.clear();
|
||||
systemGuidePressed = false;
|
||||
systemQamPressed = false;
|
||||
qamTapPressed = false;
|
||||
qamChordActive = false;
|
||||
chordButtonPressed = false;
|
||||
qamChordGeneration++;
|
||||
chordHandler.removeCallbacksAndMessages(null);
|
||||
state.clear();
|
||||
publish();
|
||||
}
|
||||
|
||||
public synchronized boolean start() {
|
||||
if (!open) {
|
||||
open = writer.open();
|
||||
@@ -393,7 +373,6 @@ public final class PadBridge {
|
||||
}
|
||||
|
||||
public synchronized void applyTouch(java.util.function.Consumer<PadState> mutation) {
|
||||
if (!builtinPad) return;
|
||||
if (touchRoute != null && touchRoute.active()) {
|
||||
mutation.accept(routedTouch);
|
||||
touchRoute.accept(routedTouch);
|
||||
@@ -433,7 +412,6 @@ public final class PadBridge {
|
||||
}
|
||||
|
||||
public synchronized void setSystemButtons(boolean guidePressed, boolean qamPressed) {
|
||||
if (!builtinPad) return;
|
||||
if (systemGuidePressed == guidePressed && systemQamPressed == qamPressed) return;
|
||||
boolean qamStarted = qamPressed && !systemQamPressed;
|
||||
systemGuidePressed = guidePressed;
|
||||
@@ -465,7 +443,6 @@ public final class PadBridge {
|
||||
}
|
||||
|
||||
public synchronized void triggerQam() {
|
||||
if (!builtinPad) return;
|
||||
if (!nativeQam()) {
|
||||
startChord(PadState.A);
|
||||
return;
|
||||
@@ -484,7 +461,6 @@ public final class PadBridge {
|
||||
}
|
||||
|
||||
public synchronized void triggerSteamKeyboard() {
|
||||
if (!builtinPad) return;
|
||||
startChord(PadState.X);
|
||||
}
|
||||
|
||||
@@ -656,7 +632,6 @@ public final class PadBridge {
|
||||
}
|
||||
|
||||
private void publish() {
|
||||
if (!builtinPad && !open && owners[0] == NO_CONTROLLER && partners[0] == NO_CONTROLLER) return;
|
||||
if (!open && !start()) {
|
||||
statDropped++;
|
||||
return;
|
||||
|
||||
@@ -211,7 +211,7 @@ object SteamTouchBindings {
|
||||
fun libraryDir(context: Context) = File(SteamTouchConfig.steamRoot(context), "tenfoot/resource/images/library/controller/binding_icons")
|
||||
|
||||
fun gameIconsDir(context: Context, appId: Int): File? = try {
|
||||
val manifest = File(SteamTouchConfig.steamRoot(context), "steamapps/appmanifest_${SteamTouchConfig.steamId(appId)}.acf")
|
||||
val manifest = File(SteamTouchConfig.steamRoot(context), "steamapps/appmanifest_$appId.acf")
|
||||
val installDir = KeyValues.parse(manifest.readText()).let { it.child("AppState") ?: it }.string("installdir")
|
||||
installDir?.let { File(SteamTouchConfig.steamRoot(context), "steamapps/common/$it/TouchMenuIcons") }?.takeIf { it.isDirectory }
|
||||
} catch (_: Exception) {
|
||||
|
||||
@@ -126,11 +126,7 @@ object SteamTouchConfig {
|
||||
}
|
||||
}
|
||||
|
||||
private val DEFAULT_SET = setOf(
|
||||
JOYSTICK_LEFT, JOYSTICK_LEFT_BUTTON, JOYSTICK_RIGHT, JOYSTICK_RIGHT_BUTTON,
|
||||
DPAD, A, B, X, Y, SELECT, START,
|
||||
BUMPER_LEFT, BUMPER_RIGHT, TRIGGER_LEFT, TRIGGER_RIGHT,
|
||||
)
|
||||
private val DEFAULT_SET = setOf(DPAD, A, B, X, Y, SELECT, START)
|
||||
private val ALWAYS = setOf(STEAM, THUMB, KEYBOARD)
|
||||
|
||||
fun steamId(appId: Int): String = Integer.toUnsignedString(appId)
|
||||
|
||||
@@ -301,8 +301,9 @@ class SteamTouchControls(
|
||||
}
|
||||
}
|
||||
SteamTouchConfig.STEAM -> {
|
||||
if (isSelected) canvas.drawCircle(x, y, r, stroke)
|
||||
drawMark(canvas, x, y, r * 0.5f, if (pressed) accent else ink, alpha)
|
||||
if (pressed) canvas.drawCircle(x, y, r, fill)
|
||||
canvas.drawCircle(x, y, r, stroke)
|
||||
drawMark(canvas, x, y, r * 0.5f, if (pressed) Color.argb(alpha, 23, 26, 33) else ink, alpha)
|
||||
}
|
||||
SteamTouchConfig.SELECT, SteamTouchConfig.START -> {
|
||||
val binding = visuals[e.type]?.firstOrNull()?.binding
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
package com.droiddeck.launcher.input
|
||||
|
||||
import android.content.Context
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import java.io.File
|
||||
|
||||
/** What SteamVR sees for the game pad. The driver reads the file once, when SteamVR starts. */
|
||||
object VrControllers {
|
||||
enum class Emulate(val key: String) { OFF("off"), INDEX("index"), TOUCH("touch"), VIVE("vive") }
|
||||
|
||||
data class Settings(val emulate: Emulate = Emulate.INDEX, val sticksMoveHands: Boolean = false)
|
||||
|
||||
const val FILE = "droiddeck-vr-controllers"
|
||||
private const val PREFS = "vr_controllers"
|
||||
|
||||
private val _settings = MutableStateFlow(Settings())
|
||||
val settings: StateFlow<Settings> = _settings.asStateFlow()
|
||||
private var loaded = false
|
||||
|
||||
fun load(context: Context): Settings {
|
||||
if (!loaded) {
|
||||
val p = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||
_settings.value = Settings(
|
||||
emulate = Emulate.entries.firstOrNull { it.key == p.getString("emulate", null) } ?: Emulate.INDEX,
|
||||
sticksMoveHands = p.getBoolean("sticksMoveHands", false),
|
||||
)
|
||||
loaded = true
|
||||
}
|
||||
return _settings.value
|
||||
}
|
||||
|
||||
fun setEmulate(context: Context, emulate: Emulate) = save(context, load(context).copy(emulate = emulate))
|
||||
fun setSticksMoveHands(context: Context, on: Boolean) = save(context, load(context).copy(sticksMoveHands = on))
|
||||
|
||||
private fun save(context: Context, settings: Settings) {
|
||||
_settings.value = settings
|
||||
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).edit()
|
||||
.putString("emulate", settings.emulate.key)
|
||||
.putBoolean("sticksMoveHands", settings.sticksMoveHands)
|
||||
.apply()
|
||||
publish(context)
|
||||
}
|
||||
|
||||
fun encode(settings: Settings): String =
|
||||
"emulate=${settings.emulate.key}\nsticks=${if (settings.sticksMoveHands) "hands" else "game"}\n"
|
||||
|
||||
/** Writes into the session's runtime dir, which the guest sees as /run/droiddeck. */
|
||||
fun publish(context: Context) {
|
||||
runCatching {
|
||||
val dir = File(context.filesDir, ".wayland-rt").apply { mkdirs() }
|
||||
val tmp = File(dir, "$FILE.tmp")
|
||||
tmp.writeText(encode(load(context)))
|
||||
tmp.renameTo(File(dir, FILE))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -181,7 +181,6 @@ object BwrapSpawner {
|
||||
request.optJSONObject("env")?.let { e -> e.keys().forEach { k -> env[k] = e.getString(k) } }
|
||||
if (binds.any { it.guest == "/dev" }) gpu(rootfs, root, env)?.forEach { addBind(it) }
|
||||
preload(session, rootfs, env).forEach { addBind(it) }
|
||||
zypakShims(binds, root, dir).forEach { addBind(it) }
|
||||
browserSandboxes(env)
|
||||
return Plan(root, binds, request.optString("cwd", "/").ifEmpty { "/" }, argv, env)
|
||||
}
|
||||
@@ -228,20 +227,6 @@ object BwrapSpawner {
|
||||
|
||||
private const val PRELOAD_DIR = "/run/droiddeck-preload"
|
||||
|
||||
private const val ZYPAK_SHIM = "#!/bin/sh\nprogram=\$1\nshift\nexec \"\$program\" --no-sandbox \"\$@\"\n"
|
||||
|
||||
private fun zypakShims(binds: List<Bind>, root: File, dir: File): List<Bind> {
|
||||
val wrappers = listOf("/app/bin/zypak-wrapper.sh", "/app/bin/zypak-wrapper").filter { guest ->
|
||||
val host = File(hostPath(binds, root.path, guest))
|
||||
host.isFile && !Files.isSymbolicLink(host.toPath())
|
||||
}
|
||||
if (wrappers.isEmpty()) return emptyList()
|
||||
val shim = File(dir, "zypak-wrapper")
|
||||
shim.writeText(ZYPAK_SHIM)
|
||||
chmod(shim, 0x1ed)
|
||||
return wrappers.map { Bind(shim.path, it) }
|
||||
}
|
||||
|
||||
private fun browserSandboxes(env: MutableMap<String, String>) {
|
||||
env.putIfAbsent("ZYPAK_ZYGOTE_STRATEGY_SPAWN", "0")
|
||||
env.putIfAbsent("QTWEBENGINE_DISABLE_SANDBOX", "1")
|
||||
|
||||
@@ -22,7 +22,6 @@ object DeckyManager {
|
||||
private const val RELEASES = "https://api.github.com/repos/$REPOSITORY/releases?per_page=30"
|
||||
private const val PATH = "root/homebrew/services/PluginLoader"
|
||||
private const val VERSION = "root/homebrew/services/.droiddeck-decky-version"
|
||||
private const val SETTINGS = "root/homebrew/settings/loader.json"
|
||||
// droiddeck-session reads the marker from the root of the client it runs.
|
||||
private val CEF_REMOTE_DEBUG_MARKERS = listOf(
|
||||
"root/.local/share/Steam/.cef-enable-remote-debugging",
|
||||
@@ -33,34 +32,7 @@ object DeckyManager {
|
||||
private set
|
||||
|
||||
data class Release(val tag: String, val prerelease: Boolean, val asset: String, val url: String, val digest: String?, val shaUrl: String?, val size: Long, val machine: Int)
|
||||
data class ReleaseChannels(val stable: List<Release>, val prerelease: List<Release>, val all: List<Release> = stable + prerelease)
|
||||
|
||||
const val BRANCH_STABLE = 0
|
||||
const val BRANCH_PRERELEASE = 1
|
||||
const val BRANCH_TESTING = 2
|
||||
|
||||
private fun settingsFile(context: Context) = File(LinuxRuntime.rootDir(context), SETTINGS)
|
||||
|
||||
fun branch(context: Context): Int {
|
||||
val saved = runCatching { JSONObject(settingsFile(context).readText()).optInt("branch", -1) }.getOrDefault(-1)
|
||||
if (saved >= 0) return saved
|
||||
val version = File(LinuxRuntime.rootDir(context), VERSION).takeIf { it.isFile }?.readText()?.trim().orEmpty()
|
||||
return if (version.startsWith("v") && "-pre" in version) BRANCH_PRERELEASE else BRANCH_STABLE
|
||||
}
|
||||
|
||||
fun setBranch(context: Context, branch: Int) {
|
||||
val file = settingsFile(context)
|
||||
val settings = runCatching { JSONObject(file.readText()) }.getOrDefault(JSONObject())
|
||||
settings.put("branch", branch)
|
||||
file.parentFile?.mkdirs()
|
||||
val staged = File(file.parentFile, "${file.name}.tmp")
|
||||
staged.writeText(settings.toString(4))
|
||||
check(staged.renameTo(file)) { "cannot write ${file.path}" }
|
||||
}
|
||||
|
||||
// Mirrors Decky's updater: Stable takes the newest non-prerelease tag, Prerelease the newest tag of either kind.
|
||||
fun latest(channels: ReleaseChannels, branch: Int): Release? =
|
||||
if (branch == BRANCH_STABLE) channels.stable.firstOrNull() else channels.all.firstOrNull()
|
||||
data class ReleaseChannels(val stable: List<Release>, val prerelease: List<Release>)
|
||||
|
||||
fun installed(context: Context): String? {
|
||||
val loader = loader(context)
|
||||
@@ -102,13 +74,17 @@ object DeckyManager {
|
||||
}
|
||||
}
|
||||
|
||||
fun releases(context: Context, prerelease: Boolean): List<Release> {
|
||||
val channels = releaseChannels(context)
|
||||
return if (prerelease) channels.prerelease else channels.stable
|
||||
}
|
||||
|
||||
fun releaseChannels(context: Context): ReleaseChannels {
|
||||
val body = Downloader.downloadString(RELEASES) ?: return ReleaseChannels(emptyList(), emptyList())
|
||||
return try {
|
||||
val array = JSONArray(body)
|
||||
val stable = mutableListOf<Release>()
|
||||
val prerelease = mutableListOf<Release>()
|
||||
val all = mutableListOf<Release>()
|
||||
for (i in 0 until array.length()) {
|
||||
val release = array.getJSONObject(i)
|
||||
if (release.optBoolean("draft", false)) continue
|
||||
@@ -129,11 +105,9 @@ object DeckyManager {
|
||||
val size = binary.optLong("size", 0)
|
||||
if (size <= 0) continue
|
||||
val row = Release(release.optString("tag_name", "unknown"), isPrerelease, name, url, digest, checksum, size, if (name == "PluginLoader-arm64") 183 else 62)
|
||||
if (!row.tag.startsWith("v")) continue
|
||||
all.add(row)
|
||||
(if (isPrerelease || "-pre" in row.tag) prerelease else stable).add(row)
|
||||
(if (isPrerelease) prerelease else stable).add(row)
|
||||
}
|
||||
ReleaseChannels(stable, prerelease, all)
|
||||
ReleaseChannels(stable, prerelease)
|
||||
} catch (e: Exception) { Log.w(TAG, "release metadata", e); ReleaseChannels(emptyList(), emptyList()) }
|
||||
}
|
||||
|
||||
|
||||
@@ -104,6 +104,7 @@ class LinuxNetworkLinkComponent(
|
||||
synchronized(lock) { writeNetworkState(currentProperties, currentCapabilities) }
|
||||
}
|
||||
}
|
||||
wifiReceiver = receiver
|
||||
appContext.registerReceiver(receiver, IntentFilter().apply {
|
||||
addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)
|
||||
addAction(WifiManager.RSSI_CHANGED_ACTION)
|
||||
@@ -111,7 +112,6 @@ class LinuxNetworkLinkComponent(
|
||||
addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION)
|
||||
addAction(LocationManager.MODE_CHANGED_ACTION)
|
||||
})
|
||||
wifiReceiver = receiver
|
||||
scanRequestStamp = File(rootDir, "etc/droiddeck-wifi-scan-request").lastModified()
|
||||
scanHandler.post(scanRequests)
|
||||
requestWifiScan()
|
||||
@@ -119,13 +119,7 @@ class LinuxNetworkLinkComponent(
|
||||
|
||||
override fun stop() {
|
||||
scanHandler.removeCallbacks(scanRequests)
|
||||
wifiReceiver?.let { receiver ->
|
||||
try {
|
||||
appContext.unregisterReceiver(receiver)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Log.w(TAG, "Wi-Fi receiver was already gone", e)
|
||||
}
|
||||
}
|
||||
wifiReceiver?.let { appContext.unregisterReceiver(it) }
|
||||
wifiReceiver = null
|
||||
val registered = synchronized(lock) { callback.also { callback = null } } ?: return
|
||||
try {
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package com.droiddeck.launcher.session
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.BatteryManager
|
||||
import android.os.Environment
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
@@ -18,35 +15,21 @@ import com.droiddeck.launcher.wayland.WaylandCompositor
|
||||
import java.io.File
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
|
||||
class PerfHud(context: Context) {
|
||||
var text by mutableStateOf("")
|
||||
private set
|
||||
var graph by mutableStateOf(FloatArray(0))
|
||||
private set
|
||||
|
||||
private val context = context.applicationContext
|
||||
private val frames = AtomicInteger()
|
||||
private var lastTick = 0L
|
||||
private var lastFps = 0f
|
||||
private var running = false
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
private val intervals = FloatArray(GRAPH)
|
||||
private val graphWrite = AtomicInteger()
|
||||
private val lastFrameNs = AtomicLong()
|
||||
|
||||
@Volatile private var showFps = true
|
||||
@Volatile private var showBattery = true
|
||||
@Volatile private var showGraph = true
|
||||
|
||||
private val enabled: Boolean
|
||||
get() = SessionPrefs.hudEnabled(context)
|
||||
&& !File(Environment.getExternalStorageDirectory(), "Download/droiddeck-no-hud").exists()
|
||||
|
||||
private val anyPart: Boolean
|
||||
get() = showFps || showBattery || showGraph
|
||||
|
||||
var onPresentingWindowChanged: (() -> Unit)? = null
|
||||
private var lastWindow: String? = null
|
||||
|
||||
@@ -57,17 +40,7 @@ class PerfHud(context: Context) {
|
||||
handler.post { onPresentingWindowChanged?.invoke() }
|
||||
}
|
||||
}
|
||||
override fun onGameFrame() {
|
||||
frames.incrementAndGet()
|
||||
if (!showGraph) return
|
||||
val now = System.nanoTime()
|
||||
val previous = lastFrameNs.getAndSet(now)
|
||||
if (previous == 0L) return
|
||||
val ms = ((now - previous) / 1_000_000f).coerceIn(0f, 50f)
|
||||
val slot = graphWrite.get()
|
||||
intervals[slot and (GRAPH - 1)] = ms
|
||||
graphWrite.compareAndSet(slot, slot + 1)
|
||||
}
|
||||
override fun onGameFrame() { frames.incrementAndGet() }
|
||||
}
|
||||
|
||||
private val tick = object : Runnable {
|
||||
@@ -76,82 +49,28 @@ class PerfHud(context: Context) {
|
||||
val now = SystemClock.elapsedRealtime()
|
||||
val dt = (now - lastTick).coerceAtLeast(1L) / 1000f
|
||||
lastTick = now
|
||||
lastFps = frames.getAndSet(0) / dt
|
||||
text = readout(lastFps)
|
||||
if (showGraph) publishGraph() else clearGraph()
|
||||
handler.postDelayed(this, TICK_MS)
|
||||
text = line(frames.getAndSet(0) / dt)
|
||||
handler.postDelayed(this, 1000)
|
||||
}
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
readOptions()
|
||||
if (!enabled || !anyPart) {
|
||||
stop()
|
||||
return
|
||||
}
|
||||
if (!running) start() else {
|
||||
if (!showGraph) clearGraph() else publishGraph()
|
||||
text = readout(lastFps)
|
||||
}
|
||||
if (enabled) start() else stop()
|
||||
}
|
||||
|
||||
fun start() {
|
||||
readOptions()
|
||||
if (!enabled || !anyPart || running) return
|
||||
if (!enabled || running) return
|
||||
running = true
|
||||
lastTick = SystemClock.elapsedRealtime()
|
||||
lastFrameNs.set(0L)
|
||||
frames.set(0)
|
||||
graphWrite.set(0)
|
||||
WaylandCompositor.setGameListener(listener)
|
||||
handler.postDelayed(tick, TICK_MS)
|
||||
handler.post(tick)
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
running = false
|
||||
handler.removeCallbacks(tick)
|
||||
WaylandCompositor.clearGameListener(listener)
|
||||
text = ""
|
||||
graph = FloatArray(0)
|
||||
}
|
||||
|
||||
private fun readOptions() {
|
||||
showFps = SessionPrefs.hudShowFps(context)
|
||||
showBattery = SessionPrefs.hudShowBattery(context)
|
||||
showGraph = SessionPrefs.hudShowGraph(context)
|
||||
}
|
||||
|
||||
private fun readout(fpsValue: Float): String {
|
||||
val parts = ArrayList<String>(2)
|
||||
if (showFps) parts.add(line(fpsValue))
|
||||
if (showBattery) batteryCelsius(readBatteryDeci())?.let { parts.add(it) }
|
||||
return parts.joinToString("\n")
|
||||
}
|
||||
|
||||
private fun readBatteryDeci(): Int {
|
||||
val sticky = runCatching {
|
||||
context.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
|
||||
}.getOrNull() ?: return 0
|
||||
return sticky.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0)
|
||||
}
|
||||
|
||||
private fun clearGraph() {
|
||||
lastFrameNs.set(0L)
|
||||
graphWrite.set(0)
|
||||
if (graph.isNotEmpty()) graph = FloatArray(0)
|
||||
}
|
||||
|
||||
private fun publishGraph() {
|
||||
val count = graphWrite.get()
|
||||
val shown = minOf(count, GRAPH)
|
||||
if (shown < 2) {
|
||||
graph = FloatArray(0)
|
||||
return
|
||||
}
|
||||
val out = FloatArray(shown)
|
||||
val start = count - shown
|
||||
for (i in 0 until shown) out[i] = intervals[(start + i) and (GRAPH - 1)]
|
||||
graph = out
|
||||
}
|
||||
|
||||
private fun line(base: Float): String {
|
||||
@@ -172,12 +91,4 @@ class PerfHud(context: Context) {
|
||||
}
|
||||
|
||||
private fun fps(value: Float) = String.format(Locale.US, "%.0f fps", value)
|
||||
|
||||
companion object {
|
||||
private const val TICK_MS = 1_000L
|
||||
private const val GRAPH = 128
|
||||
|
||||
internal fun batteryCelsius(deci: Int): String? =
|
||||
if (deci <= 0) null else String.format(Locale.US, "%.1f°C", deci / 10f)
|
||||
}
|
||||
}
|
||||
@@ -82,10 +82,8 @@ object SessionFiles {
|
||||
"usr/local/bin/droiddeck-x64-arm-webhelper" to "usr/local/bin/droiddeck-x64-arm-webhelper",
|
||||
"usr/local/bin/droiddeck-x64-webhelper" to "usr/local/bin/droiddeck-x64-webhelper",
|
||||
"usr/local/bin/droiddeck-steamvr" to "usr/local/bin/droiddeck-steamvr",
|
||||
"usr/local/bin/droiddeck-pipewire" to "usr/local/bin/droiddeck-pipewire",
|
||||
"usr/local/bin/droiddeck-steamvr-settings" to "usr/local/bin/droiddeck-steamvr-settings",
|
||||
"usr/local/bin/droiddeck-steamvr-game" to "usr/local/bin/droiddeck-steamvr-game",
|
||||
"usr/local/bin/droiddeck-steamvr-flat" to "usr/local/bin/droiddeck-steamvr-flat",
|
||||
"usr/local/bin/droiddeck-bwrap" to "usr/local/bin/droiddeck-bwrap",
|
||||
"usr/local/bin/droiddeck-flatpak" to "usr/local/bin/droiddeck-flatpak",
|
||||
"usr/local/bin/droiddeck-flatpak-setup" to "usr/local/bin/droiddeck-flatpak-setup",
|
||||
@@ -139,7 +137,6 @@ object SessionFiles {
|
||||
val optional = (arrayOf(
|
||||
"usr/local/bin/gamescope" to "usr/local/bin/gamescope",
|
||||
"usr/local/lib/droiddeck/uruntime" to "usr/local/lib/droiddeck/uruntime",
|
||||
"usr/local/lib/droiddeck-pipewire.tzst" to "usr/local/lib/droiddeck-pipewire.tzst",
|
||||
"usr/local/share/licenses/uruntime/LICENSE" to "usr/local/share/licenses/uruntime/LICENSE",
|
||||
) + mangoapp + msitools.map { it to it } + vr + fexPreloads).filter { (asset, _) ->
|
||||
val dir = asset.substringBeforeLast('/')
|
||||
|
||||
@@ -21,14 +21,12 @@ object SessionPrefs {
|
||||
const val SUSPEND_NEVER = "never"
|
||||
private const val LEGACY_SUSPEND_DOWNLOADS = "downloads"
|
||||
private const val STEAM_DOWNLOADS_IN_BACKGROUND = "steamDownloadsInBackground"
|
||||
private const val DECKY_ZIP_WARNING_SEEN = "deckyZipWarningSeen"
|
||||
|
||||
const val CONTROLLER_DECK = "deck"
|
||||
const val CONTROLLER_XBOX360 = "xbox360"
|
||||
const val CONTROLLER_STEAM = "steamcontroller"
|
||||
const val CONTROLLER_DS4 = "dualshock4"
|
||||
const val CONTROLLER_DUALSENSE = "dualsense"
|
||||
const val CONTROLLER_NONE = "none"
|
||||
const val OSC_AUTO = "auto"
|
||||
const val OSC_ALWAYS = "always"
|
||||
const val OSC_STEAM_QAM = "steam-qam"
|
||||
@@ -51,9 +49,9 @@ object SessionPrefs {
|
||||
)
|
||||
|
||||
fun orientation(context: Context): Int =
|
||||
prefs(context).getInt("orientation", ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
|
||||
prefs(context).getInt("orientation", ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE)
|
||||
.takeIf { value -> orientationOptions.any { it.first == value } }
|
||||
?: ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
||||
?: ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
||||
|
||||
fun setOrientation(context: Context, orientation: Int) {
|
||||
require(orientationOptions.any { it.first == orientation })
|
||||
@@ -158,43 +156,6 @@ object SessionPrefs {
|
||||
prefs(context).edit().putBoolean(hudKey(context), on).apply()
|
||||
}
|
||||
|
||||
fun hudShowFps(context: Context): Boolean = prefs(context).getBoolean("hudFps", true)
|
||||
|
||||
fun setHudShowFps(context: Context, on: Boolean) {
|
||||
prefs(context).edit().putBoolean("hudFps", on).apply()
|
||||
}
|
||||
|
||||
fun hudShowBattery(context: Context): Boolean = prefs(context).getBoolean("hudBattery", true)
|
||||
|
||||
fun setHudShowBattery(context: Context, on: Boolean) {
|
||||
prefs(context).edit().putBoolean("hudBattery", on).apply()
|
||||
}
|
||||
|
||||
fun hudShowGraph(context: Context): Boolean = prefs(context).getBoolean("hudGraph", true)
|
||||
|
||||
fun setHudShowGraph(context: Context, on: Boolean) {
|
||||
prefs(context).edit().putBoolean("hudGraph", on).apply()
|
||||
}
|
||||
|
||||
fun hudColor(context: Context): String =
|
||||
prefs(context).getString("hudColor", HUD_COLOR_MINT)?.takeIf { it in HUD_COLORS } ?: HUD_COLOR_MINT
|
||||
|
||||
fun setHudColor(context: Context, color: String) {
|
||||
if (color in HUD_COLORS) prefs(context).edit().putString("hudColor", color).apply()
|
||||
}
|
||||
|
||||
fun hudOpacity(context: Context): Int =
|
||||
prefs(context).getInt("hudOpacity", HUD_OPACITY_DEFAULT).takeIf { it in HUD_OPACITY_RANGE } ?: HUD_OPACITY_DEFAULT
|
||||
|
||||
fun setHudOpacity(context: Context, percent: Int) {
|
||||
prefs(context).edit().putInt("hudOpacity", percent.coerceIn(HUD_OPACITY_RANGE)).apply()
|
||||
}
|
||||
|
||||
const val HUD_COLOR_MINT = "mint"
|
||||
val HUD_COLORS = listOf(HUD_COLOR_MINT, "white", "sky", "amber", "violet")
|
||||
val HUD_OPACITY_RANGE = 30..100
|
||||
const val HUD_OPACITY_DEFAULT = 70
|
||||
|
||||
private fun hudKey(context: Context) = if (mangoappSession(context)) "hudDeck" else "hud"
|
||||
|
||||
private fun mangoappSession(context: Context) =
|
||||
@@ -459,8 +420,6 @@ object SessionPrefs {
|
||||
fun steamController(context: Context): String =
|
||||
prefs(context).getString("steamController", CONTROLLER_DECK) ?: CONTROLLER_DECK
|
||||
fun setSteamController(context: Context, id: String) { prefs(context).edit().putString("steamController", id).apply() }
|
||||
|
||||
fun builtinPad(context: Context): Boolean = steamController(context) != CONTROLLER_NONE
|
||||
fun padType(id: String): Int = when (id) {
|
||||
CONTROLLER_STEAM -> com.droiddeck.launcher.input.FakeInputWriter.PAD_TYPE_STEAM
|
||||
CONTROLLER_XBOX360 -> com.droiddeck.launcher.input.FakeInputWriter.PAD_TYPE_XBOX
|
||||
@@ -994,12 +953,6 @@ object SessionPrefs {
|
||||
}.apply()
|
||||
}
|
||||
|
||||
fun deckyZipWarningSeen(context: Context): Boolean = prefs(context).getBoolean(DECKY_ZIP_WARNING_SEEN, false)
|
||||
|
||||
fun setDeckyZipWarningSeen(context: Context) {
|
||||
prefs(context).edit().putBoolean(DECKY_ZIP_WARNING_SEEN, true).apply()
|
||||
}
|
||||
|
||||
fun steamDownloadsInBackground(context: Context): Boolean {
|
||||
val prefs = prefs(context)
|
||||
return prefs.getBoolean(STEAM_DOWNLOADS_IN_BACKGROUND, false) ||
|
||||
|
||||
@@ -71,7 +71,6 @@ class SessionService : Service() {
|
||||
private val steamDownloadMonitor = SteamDownloadMonitor()
|
||||
private var downloadMonitorTask: Runnable? = null
|
||||
private var downloadActive = false
|
||||
@Volatile private var steamLibraryMounts = emptyMap<String, File>()
|
||||
private var suspendDownloadNow = false
|
||||
private var suspendPolicy = SessionPrefs.SUSPEND_MANUAL
|
||||
private var steamDownloadsInBackground = false
|
||||
@@ -395,12 +394,6 @@ class SessionService : Service() {
|
||||
val fastPathAt = guest.size
|
||||
|
||||
val pulse = startAudio(guest, sessionDir)
|
||||
if (steamHere) {
|
||||
val encoder = VideoEncodeBridge(File(filesDir, "venc/venc.sock"))
|
||||
encoder.attach(this)
|
||||
components.add(encoder)
|
||||
guest.add("BL_VENC_SOCKET=" + encoder.socket.absolutePath)
|
||||
}
|
||||
|
||||
guest.add("BL_WIDTH=" + size.first)
|
||||
guest.add("BL_HEIGHT=" + size.second)
|
||||
@@ -709,21 +702,17 @@ class SessionService : Service() {
|
||||
|
||||
private fun addControllerEnvironment(guest: MutableList<String>, fakeInputDir: File, sessionDir: File) {
|
||||
FakeInputWriter.prepareRingSlots(fakeInputDir, 4)
|
||||
val builtinPad = SessionPrefs.builtinPad(this)
|
||||
val hiddenPad = SessionState.mode == MODE_STEAM && !builtinPad
|
||||
fakeInputDir.listFiles()?.forEach { file ->
|
||||
val node = Regex("event(\\d+)").matchEntire(file.name)?.groupValues?.get(1)?.toIntOrNull()
|
||||
if (file.name.startsWith(".uinput-") || (node != null && node >= FIRST_VIRTUAL_PAD) ||
|
||||
(hiddenPad && node == 0)) file.delete()
|
||||
if (file.name.startsWith(".uinput-") || (node != null && node >= FIRST_VIRTUAL_PAD)) file.delete()
|
||||
}
|
||||
guest.add("FAKE_EVDEV_DIR=" + fakeInputDir.path)
|
||||
val rings = FakeInputWriter.getRingEnv(fakeInputDir)
|
||||
val presented = if (hiddenPad) rings?.split(';')?.filterNot { it.startsWith("0=") }?.joinToString(";") else rings
|
||||
if (!presented.isNullOrEmpty()) guest.add("FAKE_EVDEV_MEMFD_PATHS=$presented")
|
||||
if (!rings.isNullOrEmpty()) guest.add("FAKE_EVDEV_MEMFD_PATHS=$rings")
|
||||
guest.add("FAKE_EVDEV_IDENTITY=xbox360")
|
||||
guest.add("FAKE_EVDEV_VIBRATION=1")
|
||||
val uinput = !File(Environment.getExternalStorageDirectory(), NO_UINPUT_SWITCH).exists()
|
||||
val wantsDeck = uinput && builtinPad && SessionState.mode == MODE_STEAM &&
|
||||
val wantsDeck = uinput && SessionState.mode == MODE_STEAM &&
|
||||
SessionState.steamUi != "desktop" &&
|
||||
!File(Environment.getExternalStorageDirectory(), NO_DECK_PAD_SWITCH).exists()
|
||||
val wantsTouch = wantsDeck && !File(Environment.getExternalStorageDirectory(), NO_STEAM_TOUCH_SWITCH).exists()
|
||||
@@ -813,7 +802,6 @@ class SessionService : Service() {
|
||||
File(home, "Storage").mkdirs()
|
||||
binds.add(Environment.getExternalStorageDirectory().path + ":/root/Storage")
|
||||
val library = GameStorage.effective(this)
|
||||
steamLibraryMounts = emptyMap()
|
||||
var storageDiagnosticLibrary: File? = null
|
||||
if (library != null) {
|
||||
val problem = GameStorage.prepare(this, library.path)
|
||||
@@ -825,7 +813,6 @@ class SessionService : Service() {
|
||||
val content = SecondaryLibrary.contentPath(configured)
|
||||
binds.addAll(SecondaryLibrary.binds(filesDir, configured, content = content))
|
||||
storageDiagnosticLibrary = content
|
||||
steamLibraryMounts = SecondaryLibrary.CARD_GUESTS.associateWith { content }
|
||||
val route = if (content === configured) "configured" else "verified-native"
|
||||
Log.i(TAG, "game storage: ${content.path} ($route) -> /mnt/droiddeck-sd (\"${library.label}\"); prefixes and native tools private")
|
||||
} catch (e: Exception) {
|
||||
@@ -1284,13 +1271,13 @@ class SessionService : Service() {
|
||||
|
||||
private fun updateSuspendPolicy() {
|
||||
if (!SessionState.running) return
|
||||
val hidden = !hasVisibleSurface
|
||||
val downloadsBlockingSuspend = steamDownloadsInBackground && SessionState.mode == MODE_STEAM &&
|
||||
suspendPolicy != SessionPrefs.SUSPEND_NEVER && hidden && downloadActive && !suspendDownloadNow
|
||||
if (suspendPolicy == SessionPrefs.SUSPEND_MANUAL && hidden && !downloadsBlockingSuspend) {
|
||||
if (suspendPolicy == SessionPrefs.SUSPEND_MANUAL && !hasVisibleSurface) {
|
||||
manualPauseRequested = true
|
||||
}
|
||||
val controller = suspendController ?: return
|
||||
val hidden = !hasVisibleSurface
|
||||
val downloadsBlockingSuspend = steamDownloadsInBackground && SessionState.mode == MODE_STEAM &&
|
||||
suspendPolicy != SessionPrefs.SUSPEND_NEVER && hidden && downloadActive && !suspendDownloadNow
|
||||
if (downloadsBlockingSuspend) acquireLocks()
|
||||
val steamPause = SessionState.mode == MODE_STEAM && !downloadsBlockingSuspend && when (suspendPolicy) {
|
||||
SessionPrefs.SUSPEND_AUTO, SessionPrefs.SUSPEND_NATIVE -> hidden
|
||||
@@ -1560,6 +1547,7 @@ class SessionService : Service() {
|
||||
}
|
||||
|
||||
override fun onTaskRemoved(rootIntent: Intent?) {
|
||||
if (rootIntent?.component?.className == com.droiddeck.launcher.ui.SecondScreenActivity::class.java.name) return
|
||||
if (pipTask || xrActivityVisible) {
|
||||
Log.i(TAG, "panel task dismissed - retaining the session under its active surface policy")
|
||||
activityVisible = false
|
||||
@@ -1724,7 +1712,6 @@ class SessionService : Service() {
|
||||
LinuxRuntime.rootDir(this@SessionService),
|
||||
receivedBytes = TrafficStats.getUidRxBytes(android.os.Process.myUid()),
|
||||
client = SessionState.steamClientArch,
|
||||
mounts = steamLibraryMounts,
|
||||
)
|
||||
if (wasActive != downloadActive) refreshNotification()
|
||||
updateSuspendPolicy()
|
||||
@@ -1743,9 +1730,7 @@ class SessionService : Service() {
|
||||
pads.add(String.format(java.util.Locale.ROOT, "\"%s\" (%04x:%04x)", d.name, d.vendorId, d.productId))
|
||||
}
|
||||
Log.i(TAG, "controllers at start: " + (if (pads.isEmpty()) "none" else pads.joinToString(", ")) +
|
||||
"; presented to the guest as " + if (SessionState.deckPad) "switchable, starting as " + SessionPrefs.steamController(this)
|
||||
else if (SessionState.mode == MODE_STEAM && !SessionPrefs.builtinPad(this)) "one per connected controller, no built-in pad"
|
||||
else "an Xbox 360 controller")
|
||||
"; presented to the guest as " + if (SessionState.deckPad) "switchable, starting as " + SessionPrefs.steamController(this) else "an Xbox 360 controller")
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -13,7 +13,7 @@ object SettingsReset {
|
||||
internal val SESSION_KEYS = setOf(
|
||||
"orientation", "launcherFullscreen", "animations", "theme", "runSteamAtStartup", "addedGamesArt",
|
||||
"gameStoresEnabled", "gameStoresSpeedTier", "gameStoresParallel", "storesOpenTab", "storesShowMature",
|
||||
"hud", "hudDeck", "hudFps", "hudBattery", "hudGraph", "hudColor", "hudOpacity", "backActionsInverted", "touch", "osc", "shape", "steamController",
|
||||
"hud", "hudDeck", "backActionsInverted", "touch", "osc", "shape", "steamController",
|
||||
"gamepadLeft", "gamepadRight", "pipAutoEnter", "unfoldedControls", "stretch16x9", "picturePosition",
|
||||
"gpuDriverMode", "androidDriver", "linuxDriver", "linuxDriver.steam", "linuxDriver.desktop",
|
||||
"tuSysmem", "glThread", "noGlError", "zinkLazy", "mangoapp", "gpuClockPin", "gamescopeRealtime",
|
||||
|
||||
@@ -18,9 +18,8 @@ internal class SteamDownloadMonitor {
|
||||
private var lastReceivedBytes = -1L
|
||||
private var lastReceivedAt = 0L
|
||||
|
||||
fun poll(runtimeRoot: File, now: Long = System.currentTimeMillis(), receivedBytes: Long = -1L, client: String? = null,
|
||||
mounts: Map<String, File> = emptyMap()): Boolean {
|
||||
val current = manifests(runtimeRoot, mounts).associate { it.path to read(it) }
|
||||
fun poll(runtimeRoot: File, now: Long = System.currentTimeMillis(), receivedBytes: Long = -1L, client: String? = null): Boolean {
|
||||
val current = manifests(runtimeRoot).associate { it.path to read(it) }
|
||||
val progressed = current.any { (path, state) ->
|
||||
state.incomplete && state.bytesDownloaded > (previous[path]?.bytesDownloaded ?: state.bytesDownloaded)
|
||||
}
|
||||
@@ -99,7 +98,7 @@ internal class SteamDownloadMonitor {
|
||||
now - lastContentActivityAt <= CONTENT_ACTIVITY_GRACE_MS
|
||||
}
|
||||
|
||||
private fun manifests(runtimeRoot: File, mounts: Map<String, File>): List<File> {
|
||||
private fun manifests(runtimeRoot: File): List<File> {
|
||||
val steamRoots = linkedSetOf<File>()
|
||||
for (home in STEAM_HOMES) {
|
||||
val primary = File(runtimeRoot, home)
|
||||
@@ -107,7 +106,7 @@ internal class SteamDownloadMonitor {
|
||||
val libraryFolders = File(primary, "steamapps/libraryfolders.vdf")
|
||||
runCatching {
|
||||
PATH.findAll(libraryFolders.readText()).forEach { match ->
|
||||
steamRoots += hostPath(runtimeRoot, match.groupValues[1], mounts)
|
||||
steamRoots += File(runtimeRoot, match.groupValues[1].removePrefix("/"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,15 +117,6 @@ internal class SteamDownloadMonitor {
|
||||
}
|
||||
}
|
||||
|
||||
private fun hostPath(runtimeRoot: File, guest: String, mounts: Map<String, File>): File {
|
||||
val path = guest.trimEnd('/')
|
||||
for ((mount, host) in mounts) {
|
||||
if (path == mount) return host
|
||||
if (path.startsWith("$mount/")) return File(host, path.removePrefix("$mount/"))
|
||||
}
|
||||
return File(runtimeRoot, path.removePrefix("/"))
|
||||
}
|
||||
|
||||
private fun read(file: File): ManifestState {
|
||||
val text = runCatching { file.readText() }.getOrDefault("")
|
||||
val flags = value(text, "StateFlags")?.toLongOrNull() ?: 0L
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
package com.droiddeck.launcher.session
|
||||
|
||||
import android.util.Log
|
||||
import com.droiddeck.launcher.core.SessionPart
|
||||
import java.io.File
|
||||
|
||||
/** Steam's Remote Play V4L2 encoder in the guest talks to this MediaCodec encoder (venc.c, venc_server.cpp). */
|
||||
class VideoEncodeBridge(val socket: File) : SessionPart() {
|
||||
override fun start() {
|
||||
socket.parentFile?.mkdirs()
|
||||
val ok = runCatching { System.loadLibrary("ddvenc"); nativeStart(socket.absolutePath) }
|
||||
.onFailure { Log.w(TAG, "hardware video encoding unavailable", it) }
|
||||
.getOrDefault(false)
|
||||
Log.i(TAG, if (ok) "Remote Play hardware encoding on at $socket" else "Remote Play hardware encoding off")
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
runCatching { nativeStop() }
|
||||
socket.delete()
|
||||
}
|
||||
|
||||
private external fun nativeStart(path: String): Boolean
|
||||
private external fun nativeStop()
|
||||
|
||||
private companion object {
|
||||
const val TAG = "VideoEncodeBridge"
|
||||
}
|
||||
}
|
||||
@@ -22,11 +22,8 @@ data class CatalogItem(
|
||||
val extra: Map<String, String> = emptyMap(),
|
||||
val mature: Boolean = false,
|
||||
val subscription: Boolean = false,
|
||||
val includedWith: String = "",
|
||||
) {
|
||||
val accessible: Boolean get() = owned || subscription
|
||||
/** Licensed now, or a supported package the account's subscription can install. */
|
||||
val installable: Boolean get() = accessible || includedWith.isNotBlank()
|
||||
val isDiscounted: Boolean get() = discountPercent > 0 && originalPrice.isNotBlank()
|
||||
val key: String get() = "${store.id}:$id"
|
||||
}
|
||||
@@ -89,18 +86,6 @@ internal fun retainCatalogOrder(items: List<CatalogItem>, previous: List<Catalog
|
||||
return previous.map { byKey.getValue(it.key) }
|
||||
}
|
||||
|
||||
/** A detail page opens for search results too; the library wins when it already knows the product. */
|
||||
fun detailCatalogItem(
|
||||
store: Store,
|
||||
id: String,
|
||||
library: List<CatalogItem>,
|
||||
found: List<CatalogItem>,
|
||||
shelves: StoreShelves?,
|
||||
): CatalogItem? =
|
||||
library.firstOrNull { it.store == store && it.id == id }
|
||||
?: found.firstOrNull { it.store == store && it.id == id }
|
||||
?: shelves?.all?.firstOrNull { it.store == store && it.id == id }
|
||||
|
||||
fun mergeItems(old: List<CatalogItem>?, new: List<CatalogItem>): List<CatalogItem> {
|
||||
if (old.isNullOrEmpty()) return new
|
||||
val before = old.associateBy { it.key }
|
||||
|
||||
@@ -128,10 +128,6 @@ object StoresState {
|
||||
|
||||
fun installedGame(store: Store, id: String): InstalledStoreGame? = installed.firstOrNull { it.sidecar.store == store && it.sidecar.id == id }
|
||||
|
||||
fun detailItem(store: Store, id: String): CatalogItem? = detailCatalogItem(
|
||||
store, id, library[store].orEmpty(), found[store].orEmpty(), shelves[store],
|
||||
)
|
||||
|
||||
fun open(context: Context, store: Store, force: Boolean = false) {
|
||||
val backend = backends[store] ?: return
|
||||
val app = context.applicationContext
|
||||
|
||||
@@ -10,14 +10,6 @@ import java.time.Instant
|
||||
internal object XboxCatalog {
|
||||
private val productId = Regex("[A-Z0-9]{12}")
|
||||
private val plans = setOf("CFQ7TTC0KGQ8", "CFQ7TTC0KHS0", "CFQ7TTC0P85B", "CFQ7TTC0K5DJ", "CFQ7TTC0K6L8", "CFQ7TTC10QFD")
|
||||
/** Plans that include PC games; the retired console-only plan must not offer a Windows install. */
|
||||
private val pcPlans = mapOf(
|
||||
"CFQ7TTC0KGQ8" to "PC Game Pass",
|
||||
"CFQ7TTC0KHS0" to "Game Pass Ultimate",
|
||||
"CFQ7TTC0P85B" to "Xbox Game Pass Premium",
|
||||
"CFQ7TTC0K5DJ" to "Xbox Game Pass Essential",
|
||||
"CFQ7TTC10QFD" to "Xbox Game Pass (Starter Edition)",
|
||||
)
|
||||
|
||||
fun access(json: JSONObject, account: String, market: String, now: Instant = Instant.now(), report: (String) -> Unit = {}): Map<String, String> {
|
||||
require(json.getInt("schema") == 1 && json.getString("account") == account && json.getString("market") == market)
|
||||
@@ -61,7 +53,7 @@ internal object XboxCatalog {
|
||||
val parent = row.optString("satisfiedByProductId").takeUnless { it.isBlank() || it == "null" }
|
||||
if (parent == null) return if (id in plans) null else "owned"
|
||||
if (parent in blocked) return null
|
||||
if (parent in plans) return if (parent in pcPlans && activePlan(parent)) "gamepass" else null
|
||||
if (parent in plans) return if (activePlan(parent)) "gamepass" else null
|
||||
return kind(parent, visited + id)
|
||||
}
|
||||
val result = rows.keys.mapNotNull { id -> kind(id)?.let { id to it } }.toMap()
|
||||
@@ -118,12 +110,9 @@ internal object XboxCatalog {
|
||||
}
|
||||
}
|
||||
val kind = access[id]
|
||||
val includedWith = objects(localized.optJSONObject("EligibilityProperties")?.optJSONArray("Affirmations"))
|
||||
.mapNotNull { pcPlans[it.optString("AffirmationProductId")] }
|
||||
.firstOrNull().orEmpty()
|
||||
return CatalogItem(Store.XBOX, id, title, image("SuperHeroArt", "Hero", "BoxArt"),
|
||||
tallImageUrl = image("TitledPoster", "Poster", "BoxArt"),
|
||||
owned = kind == "owned", subscription = kind == "gamepass", includedWith = includedWith,
|
||||
owned = kind == "owned", subscription = kind == "gamepass",
|
||||
storeUrl = "https://www.xbox.com/games/store/$id",
|
||||
publisher = localized.optString("PublisherName"), developer = localized.optString("DeveloperName"),
|
||||
description = localized.optString("ProductDescription"),
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.droiddeck.launcher.stores.xbox
|
||||
import android.content.Context
|
||||
import android.os.Environment
|
||||
import com.droiddeck.launcher.core.AddonPack
|
||||
import com.droiddeck.launcher.runtime.LinuxNetworkLinkComponent
|
||||
import com.droiddeck.launcher.runtime.LinuxRuntime
|
||||
import com.droiddeck.launcher.session.SessionPrefs
|
||||
import org.json.JSONObject
|
||||
@@ -15,8 +14,7 @@ internal object Xodus {
|
||||
const val DIR = "/usr/local/lib/droiddeck-xbox"
|
||||
fun market(): String = java.util.Locale.getDefault().country.uppercase().takeIf { it.matches(Regex("[A-Z]{2}")) } ?: "US"
|
||||
|
||||
class Child(val process: Process, private val credentials: XboxCredentials, private val runtimeDirectory: File? = null, private val launchDirectory: File? = null,
|
||||
private val networkLink: LinuxNetworkLinkComponent) : Closeable {
|
||||
class Child(val process: Process, private val credentials: XboxCredentials, private val runtimeDirectory: File? = null, private val launchDirectory: File? = null) : Closeable {
|
||||
@Synchronized fun send(frame: JSONObject) {
|
||||
process.outputStream.write((frame.toString() + "\n").toByteArray())
|
||||
process.outputStream.flush()
|
||||
@@ -36,7 +34,6 @@ internal object Xodus {
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
networkLink.stop()
|
||||
runtimeDirectory?.let { directory ->
|
||||
File(directory, "xodus.sock").delete()
|
||||
directory.delete()
|
||||
@@ -72,12 +69,8 @@ internal object Xodus {
|
||||
val pack = stage(app)
|
||||
val binary = "$DIR/${if (service) "xodus-service" else "xodus-cli"}"
|
||||
check(File(LinuxRuntime.rootDir(app), binary.removePrefix("/")).isFile) { "The Xbox backend is missing. Update the Linux runtime." }
|
||||
val networkLink = LinuxNetworkLinkComponent(app, LinuxRuntime.rootDir(app))
|
||||
val credentials = XboxCredentials(app, credentialGeneration)
|
||||
try {
|
||||
networkLink.attach(app)
|
||||
networkLink.publish()
|
||||
networkLink.start()
|
||||
val runtime = (runtimeDirectory ?: File(app.filesDir, "xbox-runtime")).apply { mkdirs() }
|
||||
val command = LinuxRuntime.command(app, null, null, Environment.getExternalStorageDirectory(), listOf(
|
||||
"/usr/bin/env", "HOME=/root", "XODUS_LOG=off", "RUST_LOG=off",
|
||||
@@ -99,12 +92,8 @@ internal object Xodus {
|
||||
}
|
||||
val process = builder.start()
|
||||
credentials.bind(process, File(LinuxRuntime.rootDir(app), binary.removePrefix("/")), digest.digest())
|
||||
return Child(process, credentials, runtimeDirectory, launchDirectory, networkLink)
|
||||
} catch (e: Exception) {
|
||||
runCatching { networkLink.stop() }
|
||||
credentials.close()
|
||||
throw e
|
||||
}
|
||||
return Child(process, credentials, runtimeDirectory, launchDirectory)
|
||||
} catch (e: Exception) { credentials.close(); throw e }
|
||||
}
|
||||
|
||||
fun run(app: Context, args: List<String>, timeout: Long = 90, credentialGeneration: String? = null): JSONObject {
|
||||
|
||||
@@ -40,12 +40,6 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.platform.LocalInputModeManager
|
||||
import androidx.compose.ui.input.InputMode
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import android.content.Context
|
||||
@@ -508,7 +502,7 @@ private fun ArtChooserDialog(
|
||||
sgdb = found?.sgdb ?: SteamGridDb.Status.SERVER
|
||||
}
|
||||
val first = remember { FocusRequester() }
|
||||
LaunchedEffect(options != null) { focusWithinFrames({ false }) { first } }
|
||||
LaunchedEffect(Unit) { focusWithinFrames({ false }) { first } }
|
||||
val apply: (AddedGameArt.Option) -> Unit = { option ->
|
||||
scope.launch {
|
||||
val saved = withContext(Dispatchers.IO) { target.set(app, slot, option) }
|
||||
@@ -630,23 +624,14 @@ private fun SgdbSearchDialog(
|
||||
}
|
||||
}
|
||||
LaunchedEffect(Unit) { search() }
|
||||
val firstItem = remember { FocusRequester() }
|
||||
val focusManager = LocalFocusManager.current
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
val leaveField = { focusManager.clearFocus(); keyboardController?.hide() }
|
||||
val keyboard = LocalInputModeManager.current.inputMode == InputMode.Keyboard
|
||||
LaunchedEffect(picked?.id, matches?.size, images.size, busy) {
|
||||
val ready = if (picked != null) images.isNotEmpty() else !matches.isNullOrEmpty()
|
||||
if (keyboard && !busy && ready) focusSoon { firstItem }
|
||||
}
|
||||
AppDialog(shown, back, "sgdbSearch", wide = false, maxWidth = 460.dp, modifier = Modifier.pointerInput(Unit) { detectTapGestures { leaveField() } }) {
|
||||
AppDialog(shown, back, "sgdbSearch", wide = false, maxWidth = 460.dp) {
|
||||
DialogTitleRow(stringResource(R.string.art_sgdb_title, slotName), onClose = { onCloseAll() }, leading = {
|
||||
SmallIconButton(Icons.AutoMirrored.Filled.ArrowBack, stringResource(R.string.common_back), colors.onBackground, onClick = back)
|
||||
})
|
||||
androidx.compose.material3.OutlinedTextField(
|
||||
query, { query = it }, singleLine = true, modifier = Modifier.fillMaxWidth(),
|
||||
label = { Text(stringResource(R.string.art_sgdb_search_label)) },
|
||||
keyboardActions = androidx.compose.foundation.text.KeyboardActions(onSearch = { leaveField(); search() }),
|
||||
keyboardActions = androidx.compose.foundation.text.KeyboardActions(onSearch = { search() }),
|
||||
keyboardOptions = androidx.compose.foundation.text.KeyboardOptions(imeAction = androidx.compose.ui.text.input.ImeAction.Search),
|
||||
)
|
||||
val maxHeight = (LocalConfiguration.current.screenHeightDp * 0.6f).dp
|
||||
@@ -658,24 +643,24 @@ private fun SgdbSearchDialog(
|
||||
val chosenGame = picked
|
||||
val list = matches.orEmpty()
|
||||
if (chosenGame == null) {
|
||||
list.forEachIndexed { i, match -> key(match.id) { PickerRow(null, match.name, null, focus = if (i == 0) firstItem else null) { leaveField(); showImages(match) } } }
|
||||
for (match in list) key(match.id) { PickerRow(null, match.name, null) { showImages(match) } }
|
||||
if (matches != null && list.isEmpty() && !busy && status == SteamGridDb.Status.OK) {
|
||||
Text(stringResource(R.string.art_sgdb_none), fontSize = 12.sp, color = colors.onSurfaceVariant, modifier = Modifier.padding(start = 2.dp, top = 4.dp))
|
||||
}
|
||||
} else {
|
||||
Text(chosenGame.name, fontSize = 12.sp, color = colors.onSurfaceVariant, modifier = Modifier.padding(start = 2.dp, top = 4.dp))
|
||||
FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
images.forEachIndexed { i, option -> key(option.full) {
|
||||
for (option in images) key(option.full) {
|
||||
ArtOption(
|
||||
slot, option, selected = current?.chosen == true && option.full == chosenRef, focus = if (i == 0) firstItem else null,
|
||||
slot, option, selected = current?.chosen == true && option.full == chosenRef, focus = null,
|
||||
description = "${artSourceLabel(AddedGameArt.SGDB)} $slotName",
|
||||
) { onPick(option) }
|
||||
} }
|
||||
}
|
||||
}
|
||||
if (images.isEmpty() && !busy && status == SteamGridDb.Status.OK) {
|
||||
Text(stringResource(R.string.art_sgdb_none), fontSize = 12.sp, color = colors.onSurfaceVariant, modifier = Modifier.padding(start = 2.dp, top = 4.dp))
|
||||
}
|
||||
if (list.size > 1) PickerRow(null, stringResource(R.string.art_sgdb_other_games), null) { leaveField(); picked = null }
|
||||
if (list.size > 1) PickerRow(null, stringResource(R.string.art_sgdb_other_games), null) { picked = null }
|
||||
}
|
||||
if (busy) androidx.compose.material3.LinearProgressIndicator(Modifier.fillMaxWidth().padding(vertical = 8.dp))
|
||||
}
|
||||
|
||||
@@ -69,7 +69,6 @@ import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.input.InputMode
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalInputModeManager
|
||||
@@ -559,37 +558,13 @@ private fun repoInfo(post: SteamDeckRepo.Post): String =
|
||||
listOf(post.author, "♥ ${post.likes}", "⇩ ${post.downloads}", if (post.seconds > 0) stringResource(R.string.boot_movie_seconds, post.seconds) else "")
|
||||
.filter { it.isNotEmpty() }.joinToString(" · ")
|
||||
|
||||
private const val AFTER_OPEN = 3
|
||||
private const val AFTER_INSTALL = 1
|
||||
private const val AFTER_USE = 2
|
||||
|
||||
/** Install and Use are different buttons in turn, and the button is disabled while installing; it takes focus back for the controller. */
|
||||
@Composable
|
||||
private fun RepoAction(
|
||||
installed: Boolean, inUse: Boolean, percent: Int?, busy: Boolean, modifier: Modifier, onInstall: () -> Unit, onUse: () -> Unit,
|
||||
takeFocus: Boolean = false,
|
||||
) {
|
||||
val requester = remember { FocusRequester() }
|
||||
val focused = remember { booleanArrayOf(false) }
|
||||
var pending by remember { mutableIntStateOf(if (takeFocus) AFTER_OPEN else 0) }
|
||||
val inputMode = LocalInputModeManager.current
|
||||
LaunchedEffect(installed, inUse, busy, percent != null, pending) {
|
||||
if (pending == 0) return@LaunchedEffect
|
||||
if (inputMode.inputMode != InputMode.Keyboard) { pending = 0; return@LaunchedEffect }
|
||||
focusWithinFrames({ focused[0] }) { requester }
|
||||
when (pending) {
|
||||
AFTER_OPEN -> pending = 0
|
||||
AFTER_INSTALL -> if (installed && focused[0]) pending = 0
|
||||
AFTER_USE -> if (inUse && focused[0]) pending = 0
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
val m = modifier.focusRequester(requester).onFocusChanged { focused[0] = it.hasFocus || it.isFocused }
|
||||
if (inUse) SecondaryButton(stringResource(R.string.boot_movie_in_use), compact = true, modifier = m, onClick = {})
|
||||
else if (installed) SecondaryButton(stringResource(R.string.boot_movie_repo_use), compact = true, modifier = m, onClick = { pending = AFTER_USE; onUse() })
|
||||
private fun RepoAction(installed: Boolean, inUse: Boolean, percent: Int?, busy: Boolean, modifier: Modifier, onInstall: () -> Unit, onUse: () -> Unit) {
|
||||
if (inUse) SecondaryButton(stringResource(R.string.boot_movie_in_use), compact = true, modifier = modifier, onClick = {})
|
||||
else if (installed) SecondaryButton(stringResource(R.string.boot_movie_repo_use), compact = true, modifier = modifier, onClick = onUse)
|
||||
else SecondaryButton(
|
||||
if (percent != null) stringResource(R.string.boot_movie_converting, percent) else stringResource(R.string.boot_movie_repo_install),
|
||||
enabled = !busy, compact = true, modifier = m, onClick = { pending = AFTER_INSTALL; onInstall() },
|
||||
enabled = !busy, compact = true, modifier = modifier, onClick = onInstall,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -601,6 +576,9 @@ private fun PreviewDialog(
|
||||
) {
|
||||
val shown = rememberShown(onDismiss)
|
||||
val close = { shown.targetState = false }
|
||||
val action = remember { FocusRequester() }
|
||||
val inputMode = LocalInputModeManager.current
|
||||
LaunchedEffect(Unit) { if (inputMode.inputMode == InputMode.Keyboard) focusSoon { action } }
|
||||
val maxVideoHeight = (LocalConfiguration.current.screenHeightDp - 200).coerceAtLeast(120).dp
|
||||
AppDialog(shown, close, "boot-movie-preview", wide = false, maxWidth = 620.dp) {
|
||||
DialogTitleRow(post.title, close)
|
||||
@@ -608,7 +586,7 @@ private fun PreviewDialog(
|
||||
MoviePreview(post.video, Modifier.heightIn(max = maxVideoHeight).aspectRatio(16f / 9f))
|
||||
}
|
||||
Text(repoInfo(post), fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
RepoAction(installed, inUse, percent, busy, Modifier.fillMaxWidth(), onInstall, onUse, takeFocus = true)
|
||||
RepoAction(installed, inUse, percent, busy, Modifier.fillMaxWidth().focusRequester(action), onInstall, onUse)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -268,21 +268,6 @@ private fun ManageSaves(g: Library.SteamGame, prefix: java.io.File, a: FrontEndA
|
||||
}
|
||||
}
|
||||
|
||||
/** A skipped sync is not a success: "0 files" used to show for a signed-out store or cloud saves turned off. */
|
||||
internal fun cloudMessage(context: android.content.Context, r: com.droiddeck.launcher.stores.CloudSaves.Result): String = when (r.result) {
|
||||
"ok" -> when {
|
||||
r.conflicts > 0 -> context.getString(R.string.cloud_conflict_files, r.conflicts)
|
||||
r.files == 0 -> context.getString(R.string.cloud_up_to_date)
|
||||
else -> context.getString(R.string.cloud_done, r.files)
|
||||
}
|
||||
"skipped" -> context.getString(when (r.reason) {
|
||||
"not-signed-in" -> R.string.cloud_signed_out
|
||||
"no-cloud-saves" -> R.string.cloud_none
|
||||
else -> R.string.cloud_not_synced
|
||||
})
|
||||
else -> context.getString(R.string.cloud_not_synced)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CloudRows(g: Library.SteamGame, open: Boolean, first: androidx.compose.ui.focus.FocusRequester) {
|
||||
val store = com.droiddeck.launcher.stores.Store.byId(g.source)?.takeIf { it != com.droiddeck.launcher.stores.Store.AMAZON } ?: return
|
||||
@@ -305,7 +290,7 @@ private fun CloudRows(g: Library.SteamGame, open: Boolean, first: androidx.compo
|
||||
busy = if (up) "up" else "down"
|
||||
Thread({
|
||||
val r = if (up) com.droiddeck.launcher.stores.CloudSaves.upload(context, store, id, force) else com.droiddeck.launcher.stores.CloudSaves.download(context, store, id, force)
|
||||
com.droiddeck.launcher.stores.StoresState.post { busy = null; tick++; android.widget.Toast.makeText(context, cloudMessage(context, r), android.widget.Toast.LENGTH_SHORT).show() }
|
||||
com.droiddeck.launcher.stores.StoresState.post { busy = null; tick++; android.widget.Toast.makeText(context, if (r.ok) context.getString(R.string.cloud_done, r.files) else r.reason, android.widget.Toast.LENGTH_SHORT).show() }
|
||||
}, "cloud-manual").start()
|
||||
}
|
||||
MenuItem(stringResource(R.string.store_cloud_saves), checked = enabled && supported, enabled = supported, focusRequester = first,
|
||||
@@ -317,7 +302,7 @@ private fun CloudRows(g: Library.SteamGame, open: Boolean, first: androidx.compo
|
||||
com.droiddeck.launcher.stores.StoresState.post { enabled = written?.cloud ?: enabled }
|
||||
}, "cloud-switch").start()
|
||||
}
|
||||
val ready = st != null && supported && enabled && busy == null
|
||||
val ready = st != null && supported && busy == null
|
||||
val pending = stringResource(R.string.cloud_pending)
|
||||
val summary = when {
|
||||
st == null || busy != null -> pending
|
||||
|
||||
@@ -132,7 +132,7 @@ class FrontEndState(
|
||||
val controller: com.droiddeck.launcher.input.ControllerPrefs.Settings? = null,
|
||||
val phantomProcessStatus: PhantomProcessStatus = PhantomProcessStatus.NOT_APPLICABLE,
|
||||
val showPhantomGate: Boolean = false,
|
||||
val orientation: Int = android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
|
||||
val orientation: Int = android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE,
|
||||
val launcherFullscreen: Boolean = true,
|
||||
val downloadSource: String = DownloadSource.MODE_AUTO,
|
||||
val downloadMirror: String = "",
|
||||
|
||||
@@ -79,33 +79,6 @@ internal object GlassesTab {
|
||||
const val VR = "vr"
|
||||
const val HARDWARE = "hardware"
|
||||
const val TRACKING = "tracking"
|
||||
const val CONTROLLERS = "controllers"
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun VrControllerGroups(host: MenuHost, track: (String) -> Modifier = { Modifier }) {
|
||||
val context = androidx.compose.ui.platform.LocalContext.current
|
||||
androidx.compose.runtime.LaunchedEffect(Unit) { com.droiddeck.launcher.input.VrControllers.load(context) }
|
||||
val c by com.droiddeck.launcher.input.VrControllers.settings.collectAsState()
|
||||
val emulate = com.droiddeck.launcher.input.VrControllers.Emulate.entries
|
||||
SettingsGroup(stringResource(R.string.vr_controllers_group)) {
|
||||
ChoiceRow(host, "vr-emulate", stringResource(R.string.vr_controllers_emulate), stringResource(R.string.vr_controllers_emulate_hint),
|
||||
emulate.map { it to stringResource(when (it) {
|
||||
com.droiddeck.launcher.input.VrControllers.Emulate.OFF -> R.string.vr_controllers_off
|
||||
com.droiddeck.launcher.input.VrControllers.Emulate.INDEX -> R.string.vr_controllers_index
|
||||
com.droiddeck.launcher.input.VrControllers.Emulate.TOUCH -> R.string.vr_controllers_touch
|
||||
com.droiddeck.launcher.input.VrControllers.Emulate.VIVE -> R.string.vr_controllers_vive
|
||||
}) }, c.emulate, chipModifier = track("vr-emulate")) { com.droiddeck.launcher.input.VrControllers.setEmulate(context, it) }
|
||||
if (c.emulate != com.droiddeck.launcher.input.VrControllers.Emulate.OFF) ChoiceRow(host, "vr-sticks",
|
||||
stringResource(R.string.vr_controllers_sticks), stringResource(R.string.vr_controllers_sticks_hint),
|
||||
listOf(false to stringResource(R.string.vr_controllers_sticks_game), true to stringResource(R.string.vr_controllers_sticks_hands)),
|
||||
c.sticksMoveHands, chipModifier = track("vr-sticks")) { com.droiddeck.launcher.input.VrControllers.setSticksMoveHands(context, it) }
|
||||
SettingsRow(stringResource(R.string.vr_controllers_buttons), stringResource(when (c.emulate) {
|
||||
com.droiddeck.launcher.input.VrControllers.Emulate.OFF -> R.string.vr_controllers_buttons_off
|
||||
com.droiddeck.launcher.input.VrControllers.Emulate.VIVE -> R.string.vr_controllers_buttons_vive
|
||||
else -> R.string.vr_controllers_buttons_stick
|
||||
}), hintLines = 6) {}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -389,3 +362,13 @@ private fun GlassesRecenterAction(label: String, tag: String, tint: Int, opacity
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun GlassesPlayingHint(model: String) {
|
||||
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Icon(GlassesIcon, null, tint = Color.White.copy(alpha = 0.35f), modifier = Modifier.size(56.dp))
|
||||
Spacer(Modifier.height(10.dp))
|
||||
Text(stringResource(R.string.glasses_playing, model), color = Color.White.copy(alpha = 0.45f), fontSize = 15.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package com.droiddeck.launcher.ui
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.displayCutout
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.droiddeck.launcher.R
|
||||
|
||||
/** Actions for the phone while the picture is on the glasses. Null hides the button. */
|
||||
internal class GlassesPhoneActions(
|
||||
val onRecenter: (() -> Unit)?,
|
||||
val onRecenterJoyCons: (() -> Unit)?,
|
||||
val onSteamVrMenu: (() -> Unit)?,
|
||||
val onSwitchSticks: (() -> Unit)?,
|
||||
val controllers: String?,
|
||||
val onNextControllers: (() -> Unit)?,
|
||||
val onKeyboard: () -> Unit,
|
||||
val onSessionMenu: () -> Unit,
|
||||
val onStopSteamVr: (() -> Unit)?,
|
||||
val brightness: Int,
|
||||
val brightnessMax: Int,
|
||||
val onBrightness: ((Int) -> Unit)?,
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
internal fun GlassesPhonePanel(model: String, status: String?, a: GlassesPhoneActions) {
|
||||
Box(Modifier.fillMaxSize().windowInsetsPadding(WindowInsets.displayCutout).padding(16.dp), contentAlignment = Alignment.Center) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.widthIn(max = 640.dp)) {
|
||||
Icon(GlassesIcon, null, tint = Color.White.copy(alpha = 0.35f), modifier = Modifier.size(40.dp))
|
||||
Spacer(Modifier.height(6.dp))
|
||||
Text(stringResource(R.string.glasses_playing, model), color = Color.White.copy(alpha = 0.6f), fontSize = 15.sp)
|
||||
if (status != null) Text(status, color = Color.White.copy(alpha = 0.4f), fontSize = 13.sp, textAlign = TextAlign.Center)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
FlowRow(horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.CenterHorizontally),
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
a.onRecenter?.let { PanelButton(stringResource(R.string.glasses_phone_recenter), "phone-recenter", it) }
|
||||
a.onRecenterJoyCons?.let { PanelButton(stringResource(R.string.glasses_recenter_joycons_button), "phone-recenter-joycons", it) }
|
||||
a.onSteamVrMenu?.let { PanelButton(stringResource(R.string.glasses_phone_steamvr_menu), "phone-steamvr-menu", it) }
|
||||
a.onNextControllers?.let { next -> a.controllers?.let {
|
||||
PanelButton(stringResource(R.string.glasses_phone_controllers, it), "phone-controllers", next) } }
|
||||
a.onSwitchSticks?.let { PanelButton(stringResource(R.string.glasses_phone_switch_sticks), "phone-switch-sticks", it) }
|
||||
PanelButton(stringResource(R.string.glasses_phone_keyboard), "phone-keyboard", a.onKeyboard)
|
||||
PanelButton(stringResource(R.string.glasses_phone_session_menu), "phone-session-menu", a.onSessionMenu)
|
||||
a.onStopSteamVr?.let { PanelButton(stringResource(R.string.glasses_phone_stop_steamvr), "phone-stop-steamvr", it) }
|
||||
}
|
||||
val setBrightness = a.onBrightness
|
||||
if (setBrightness != null && a.brightnessMax > 0) {
|
||||
Spacer(Modifier.height(14.dp))
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
PanelButton("−", "phone-brightness-down") { setBrightness((a.brightness - 1).coerceAtLeast(0)) }
|
||||
Text(stringResource(R.string.glasses_phone_brightness, a.brightness.coerceAtLeast(0), a.brightnessMax),
|
||||
color = Color.White.copy(alpha = 0.6f), fontSize = 14.sp)
|
||||
PanelButton("+", "phone-brightness-up") { setBrightness((a.brightness + 1).coerceAtMost(a.brightnessMax)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PanelButton(label: String, tag: String, onClick: () -> Unit) =
|
||||
SecondaryButton(label, compact = true, modifier = Modifier.testTag(tag), onClick = onClick)
|
||||
@@ -3,9 +3,6 @@ package com.droiddeck.launcher.ui
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.foundation.hoverable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -108,7 +105,7 @@ private fun LogoPreview(hero: File?, logo: File?, value: LogoPosition.Value) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PinChip(label: String, selected: Boolean, focus: FocusRequester?, onClick: () -> Unit) {
|
||||
private fun PinChip(label: String, selected: Boolean, onClick: () -> Unit) {
|
||||
val pal = LocalPalette.current
|
||||
val colors = MaterialTheme.colorScheme
|
||||
val src = remember { MutableInteractionSource() }
|
||||
@@ -116,7 +113,7 @@ private fun PinChip(label: String, selected: Boolean, focus: FocusRequester?, on
|
||||
val shape = RoundedCornerShape(8.dp)
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier.then(if (focus != null) Modifier.focusRequester(focus) else Modifier).clip(shape)
|
||||
modifier = Modifier.clip(shape)
|
||||
.background(if (selected) pal.signal.copy(alpha = 0.14f) else Color.White.copy(alpha = 0.03f))
|
||||
.border(if (selected) 2.dp else 1.dp, if (selected) pal.signal else pal.line, shape)
|
||||
.glideBorder(hot, shape, pal.signal)
|
||||
@@ -143,9 +140,6 @@ internal fun LogoPositionDialog(
|
||||
val shown = rememberShown(onBack)
|
||||
val back = { shown.targetState = false }
|
||||
var value by remember { mutableStateOf(start ?: LogoPosition.DEFAULT) }
|
||||
val startPin = remember { value.pin }
|
||||
val first = remember { FocusRequester() }
|
||||
LaunchedEffect(Unit) { focusSoon { first } }
|
||||
var failed by remember { mutableStateOf(false) }
|
||||
var busy by remember { mutableStateOf(false) }
|
||||
val save: (LogoPosition.Value?) -> Unit = { next ->
|
||||
@@ -178,7 +172,7 @@ internal fun LogoPositionDialog(
|
||||
)
|
||||
Text(stringResource(R.string.logo_pos_position), fontSize = 12.sp, color = colors.onSurfaceVariant, modifier = Modifier.padding(start = 2.dp, top = 2.dp))
|
||||
FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
for (pin in LogoPosition.Pin.values()) PinChip(pin.label(), pin == value.pin, if (pin == startPin) first else null) { value = value.copy(pin = pin) }
|
||||
for (pin in LogoPosition.Pin.values()) PinChip(pin.label(), pin == value.pin) { value = value.copy(pin = pin) }
|
||||
}
|
||||
ValueSlider(
|
||||
value.widthPct, LogoPosition.MIN_PCT..LogoPosition.MAX_PCT, step = 1, enabled = true, label = stringResource(R.string.logo_pos_size),
|
||||
|
||||
@@ -14,7 +14,6 @@ import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
@@ -99,7 +98,6 @@ class ModeSettings(
|
||||
val wifiDiscoveryBlocked: Boolean = false,
|
||||
val deckyInstalled: String? = null,
|
||||
val deckyLatestRelease: DeckyManager.Release? = null,
|
||||
val deckyBranch: Int = DeckyManager.BRANCH_STABLE,
|
||||
val deckyChecking: Boolean = false,
|
||||
val deckyStage: String? = null,
|
||||
val deckyPercent: Int = -1,
|
||||
@@ -146,7 +144,6 @@ class ModeSettingsActions(
|
||||
val onWifiDiscoverySettings: () -> Unit = {},
|
||||
val onDeckyInstall: (DeckyManager.Release) -> Unit = {},
|
||||
val onDeckyCheck: () -> Unit = {},
|
||||
val onDeckyBranch: (Int) -> Unit = {},
|
||||
val onDeckyEnabled: (Boolean) -> Unit = {},
|
||||
val onDeckyUninstall: () -> Unit = {},
|
||||
val onPickDeckyPluginZip: () -> Unit = {},
|
||||
@@ -276,10 +273,8 @@ fun ModeSheetRows(s: ModeSettings, a: ModeSettingsActions, tab: String, extras:
|
||||
SessionPrefs.CONTROLLER_DS4 to stringResource(R.string.mode_controller_ds4),
|
||||
SessionPrefs.CONTROLLER_DUALSENSE to stringResource(R.string.mode_controller_dualsense),
|
||||
SessionPrefs.CONTROLLER_XBOX360 to stringResource(R.string.mode_controller_x360),
|
||||
SessionPrefs.CONTROLLER_NONE to stringResource(R.string.second_mode_none),
|
||||
), s.steamController,
|
||||
note = if (s.steamController == SessionPrefs.CONTROLLER_NONE) stringResource(R.string.common_applies_next_session)
|
||||
else stringResource(R.string.mode_controller_note),
|
||||
note = stringResource(R.string.mode_controller_note),
|
||||
onPick = a.onSteamController,
|
||||
)
|
||||
if (steam && s.unfoldedControls != null) ToggleRow(
|
||||
@@ -428,19 +423,6 @@ fun ModeSheetRows(s: ModeSettings, a: ModeSettingsActions, tab: String, extras:
|
||||
if (s.deckyInstalled != null) DeckyUninstall(enabled = s.deckyStage == null && !s.deckySessionRunning, onUninstall = a.onDeckyUninstall)
|
||||
}
|
||||
}
|
||||
ChoiceRow(
|
||||
host, "decky-channel", stringResource(R.string.decky_channel),
|
||||
stringResource(R.string.decky_channel_hint),
|
||||
listOf(
|
||||
DeckyManager.BRANCH_STABLE to stringResource(R.string.decky_channel_stable),
|
||||
DeckyManager.BRANCH_PRERELEASE to stringResource(R.string.decky_channel_prerelease),
|
||||
) + if (s.deckyBranch == DeckyManager.BRANCH_TESTING) listOf(
|
||||
DeckyManager.BRANCH_TESTING to stringResource(R.string.decky_channel_testing),
|
||||
) else emptyList(),
|
||||
s.deckyBranch,
|
||||
enabled = s.deckyStage == null && !s.deckySessionRunning,
|
||||
onPick = a.onDeckyBranch,
|
||||
)
|
||||
if (s.deckyInstalled != null) ToggleRow(
|
||||
host, "decky-enabled", stringResource(R.string.mode_decky),
|
||||
if (s.deckyEnabled) stringResource(R.string.mode_decky_on)
|
||||
@@ -547,35 +529,6 @@ internal fun ProtonOptions(
|
||||
GameEnvironmentRow()
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun DeckyZipWarningDialog(onConfirm: () -> Unit, onCancel: () -> Unit) {
|
||||
val context = androidx.compose.ui.platform.LocalContext.current
|
||||
var left by remember { mutableIntStateOf(if (SessionPrefs.deckyZipWarningSeen(context)) 0 else DECKY_ZIP_WARNING_SECONDS) }
|
||||
androidx.compose.runtime.LaunchedEffect(Unit) {
|
||||
while (left > 0) {
|
||||
kotlinx.coroutines.delay(1000L)
|
||||
left--
|
||||
}
|
||||
SessionPrefs.setDeckyZipWarningSeen(context)
|
||||
}
|
||||
AlertDialog(
|
||||
onDismissRequest = onCancel,
|
||||
title = { Text(stringResource(R.string.decky_zip_warning_title)) },
|
||||
text = { Text(stringResource(R.string.decky_zip_warning_text)) },
|
||||
confirmButton = {
|
||||
androidx.compose.material3.TextButton(onClick = onConfirm, enabled = left == 0) {
|
||||
Text(
|
||||
if (left > 0) androidx.compose.ui.res.pluralStringResource(R.plurals.decky_zip_warning_wait, left, left)
|
||||
else stringResource(R.string.decky_zip_warning_continue),
|
||||
)
|
||||
}
|
||||
},
|
||||
dismissButton = { androidx.compose.material3.TextButton(onClick = onCancel) { Text(stringResource(R.string.common_cancel)) } },
|
||||
)
|
||||
}
|
||||
|
||||
private const val DECKY_ZIP_WARNING_SECONDS = 5
|
||||
|
||||
@Composable
|
||||
private fun DeckyUninstall(enabled: Boolean, onUninstall: () -> Unit) {
|
||||
val colors = MaterialTheme.colorScheme
|
||||
|
||||
+11
-52
@@ -1,21 +1,17 @@
|
||||
package com.droiddeck.launcher.ui
|
||||
|
||||
import com.droiddeck.launcher.R
|
||||
import android.app.Presentation
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.Display
|
||||
import android.view.Gravity
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewConfiguration
|
||||
import android.view.WindowManager
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Button
|
||||
import android.widget.FrameLayout
|
||||
@@ -37,9 +33,9 @@ import com.termux.terminal.TerminalSessionClient
|
||||
import com.termux.view.TerminalView
|
||||
import com.termux.view.TerminalViewClient
|
||||
|
||||
class SecondScreenPresentation(
|
||||
internal class SecondScreenControls(
|
||||
context: Context,
|
||||
private val sessionActivity: SessionActivity,
|
||||
display: Display,
|
||||
private val sendPointer: (x: Float, y: Float, width: Float, height: Float) -> Unit,
|
||||
private val sendButton: (button: Int, pressed: Boolean) -> Unit,
|
||||
private val sendWheel: (steps: Int) -> Unit,
|
||||
@@ -47,12 +43,8 @@ class SecondScreenPresentation(
|
||||
private val onQam: () -> Unit,
|
||||
private val onClose: () -> Unit,
|
||||
private val pad: PadBridge? = null,
|
||||
) : Presentation(sessionActivity, display) {
|
||||
private val root = LinearLayout(context).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
setBackgroundColor(Color.rgb(15, 18, 22))
|
||||
isFocusableInTouchMode = true
|
||||
}
|
||||
) : LinearLayout(context) {
|
||||
private val root get() = this
|
||||
private var currentMode = SecondScreenMode.NONE
|
||||
private var keyboardHost: KeyboardHost? = null
|
||||
private var terminalSession: TerminalSession? = null
|
||||
@@ -60,35 +52,22 @@ class SecondScreenPresentation(
|
||||
private var terminalPid = -1
|
||||
private var ctrlActive = false
|
||||
private var altActive = false
|
||||
private var created = false
|
||||
|
||||
init {
|
||||
setCancelable(false)
|
||||
setCanceledOnTouchOutside(false)
|
||||
setOnDismissListener { closeTerminal() }
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
created = true
|
||||
window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
||||
setContentView(root)
|
||||
applyImmersiveMode()
|
||||
renderMode()
|
||||
orientation = VERTICAL
|
||||
setBackgroundColor(Color.rgb(15, 18, 22))
|
||||
isFocusableInTouchMode = true
|
||||
}
|
||||
|
||||
fun showMode(mode: SecondScreenMode) {
|
||||
if (currentMode == mode && isShowing) return
|
||||
if (currentMode == mode) return
|
||||
currentMode = mode
|
||||
if (created) renderMode()
|
||||
renderMode()
|
||||
}
|
||||
|
||||
fun closeControls() {
|
||||
closeTerminal()
|
||||
keyboardHost?.hide()
|
||||
keyboardHost = null
|
||||
dismiss()
|
||||
}
|
||||
|
||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||
@@ -98,24 +77,14 @@ class SecondScreenPresentation(
|
||||
return super.dispatchKeyEvent(event)
|
||||
}
|
||||
|
||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
super.onWindowFocusChanged(hasFocus)
|
||||
if (hasFocus) {
|
||||
applyImmersiveMode()
|
||||
sessionActivity.refreshClipboard()
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderMode() {
|
||||
keyboardHost?.hide()
|
||||
keyboardHost = null
|
||||
closeTerminal()
|
||||
root.removeAllViews()
|
||||
val mode = if (currentMode == SecondScreenMode.DECK_CONTROLS && !SessionState.deckPad) SecondScreenMode.NONE
|
||||
else currentMode
|
||||
if (mode == SecondScreenMode.NONE) return
|
||||
if (currentMode == SecondScreenMode.NONE) return
|
||||
|
||||
when (mode) {
|
||||
when (currentMode) {
|
||||
SecondScreenMode.KEYBOARD_TRACKPAD -> renderKeyboardAndTrackpad()
|
||||
SecondScreenMode.TERMINAL -> renderTerminal()
|
||||
SecondScreenMode.DECK_CONTROLS -> root.addView(DeckControlsPanel(context, onClose, onSteamMenu, onQam, pad), weightParams())
|
||||
@@ -293,16 +262,6 @@ class SecondScreenPresentation(
|
||||
|
||||
private fun weightParams() = LinearLayout.LayoutParams(-1, 0, 1f)
|
||||
|
||||
private fun applyImmersiveMode() {
|
||||
@Suppress("DEPRECATION")
|
||||
window?.decorView?.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
or View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)
|
||||
}
|
||||
|
||||
private inner class TrackpadView(context: Context) : View(context) {
|
||||
private val gestures = TouchpadGestures(ViewConfiguration.get(context).scaledTouchSlop.toFloat(),
|
||||
object : PointerGestures.Listener {
|
||||
@@ -0,0 +1,139 @@
|
||||
package com.droiddeck.launcher.ui
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.ActivityOptions
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.Display
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import com.droiddeck.launcher.SessionActivity
|
||||
import com.droiddeck.launcher.input.PadBridge
|
||||
import com.droiddeck.launcher.input.SecondScreenMode
|
||||
import com.droiddeck.launcher.session.SessionState
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
|
||||
class SecondScreenWindow(
|
||||
private val sessionActivity: SessionActivity,
|
||||
val display: Display,
|
||||
private val sendPointer: (Float, Float, Float, Float) -> Unit,
|
||||
private val sendButton: (Int, Boolean) -> Unit,
|
||||
private val sendWheel: (Int) -> Unit,
|
||||
private val onSteamMenu: () -> Unit,
|
||||
private val onQam: () -> Unit,
|
||||
private val onClose: () -> Unit,
|
||||
private val pad: PadBridge? = null,
|
||||
) {
|
||||
private val token = tokens.incrementAndGet()
|
||||
private var activity = WeakReference<SecondScreenActivity>(null)
|
||||
private var controls: SecondScreenControls? = null
|
||||
private var mode = SecondScreenMode.NONE
|
||||
private var launching = false
|
||||
private var closed = false
|
||||
val window get() = activity.get()?.window
|
||||
val isShowing get() = !closed && (launching || activity.get()?.isFinishing == false)
|
||||
|
||||
fun show() {
|
||||
if (isShowing || closed) return
|
||||
owners[token] = WeakReference(this)
|
||||
launching = true
|
||||
try {
|
||||
val intent = Intent(sessionActivity, SecondScreenActivity::class.java)
|
||||
.putExtra(OWNER, token)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
|
||||
sessionActivity.startActivity(intent, ActivityOptions.makeBasic().setLaunchDisplayId(display.displayId).toBundle())
|
||||
} catch (error: Exception) {
|
||||
launching = false
|
||||
owners.remove(token)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
fun showMode(mode: SecondScreenMode) {
|
||||
this.mode = mode
|
||||
controls?.showMode(mode)
|
||||
}
|
||||
|
||||
fun closeControls() {
|
||||
closed = true
|
||||
launching = false
|
||||
owners.remove(token)
|
||||
controls?.closeControls()
|
||||
controls = null
|
||||
activity.get()?.finishAndRemoveTask()
|
||||
activity.clear()
|
||||
}
|
||||
|
||||
internal fun attach(host: SecondScreenActivity): View {
|
||||
controls?.closeControls()
|
||||
activity = WeakReference(host)
|
||||
launching = false
|
||||
return SecondScreenControls(host, sessionActivity, sendPointer, sendButton, sendWheel,
|
||||
onSteamMenu, onQam, onClose, pad).also {
|
||||
controls = it
|
||||
it.showMode(mode)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun detach(host: SecondScreenActivity) {
|
||||
if (activity.get() !== host) return
|
||||
controls?.closeControls()
|
||||
controls = null
|
||||
activity.clear()
|
||||
if (host.isFinishing && !closed) onClose()
|
||||
}
|
||||
|
||||
internal fun refreshClipboard() = sessionActivity.refreshClipboard()
|
||||
|
||||
companion object {
|
||||
private const val OWNER = "secondScreenOwner"
|
||||
private val tokens = AtomicLong()
|
||||
private val owners = mutableMapOf<Long, WeakReference<SecondScreenWindow>>()
|
||||
internal fun find(intent: Intent): SecondScreenWindow? =
|
||||
owners[intent.getLongExtra(OWNER, 0)]?.get()?.takeUnless { it.closed }
|
||||
}
|
||||
}
|
||||
|
||||
class SecondScreenActivity : Activity() {
|
||||
private var owner: SecondScreenWindow? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val host = SecondScreenWindow.find(intent)
|
||||
if (host == null || !SessionState.running) {
|
||||
finishAndRemoveTask()
|
||||
return
|
||||
}
|
||||
owner = host
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
||||
setContentView(host.attach(this))
|
||||
applyImmersiveMode()
|
||||
}
|
||||
|
||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
super.onWindowFocusChanged(hasFocus)
|
||||
if (hasFocus) {
|
||||
applyImmersiveMode()
|
||||
owner?.refreshClipboard()
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Back is handled by the session controls")
|
||||
override fun onBackPressed() = Unit
|
||||
|
||||
override fun onDestroy() {
|
||||
owner?.detach(this)
|
||||
owner = null
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun applyImmersiveMode() {
|
||||
@Suppress("DEPRECATION")
|
||||
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,6 @@ import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.material.icons.filled.Pause
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
@@ -69,8 +68,6 @@ import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Path
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
@@ -81,93 +78,21 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
|
||||
|
||||
@Composable
|
||||
fun HudText(text: String, graph: FloatArray = FloatArray(0), color: String = "mint", opacity: Int = 70) {
|
||||
if (text.isEmpty() && graph.size < 2) return
|
||||
val accent = hudInk(color)
|
||||
val wash = (opacity.coerceIn(30, 100) / 100f)
|
||||
val ink = accent.copy(alpha = (0.5f + wash * 0.5f).coerceIn(0.62f, 1f))
|
||||
fun HudText(text: String) {
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.TopEnd) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.End,
|
||||
Text(
|
||||
text,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontSize = 12.sp,
|
||||
color = Color.White,
|
||||
modifier = Modifier
|
||||
.padding(12.dp)
|
||||
.background(
|
||||
Brush.verticalGradient(
|
||||
listOf(accent.copy(alpha = wash * 0.28f), Color.Black.copy(alpha = wash * 0.72f)),
|
||||
),
|
||||
RoundedCornerShape(8.dp),
|
||||
)
|
||||
.padding(horizontal = 8.dp, vertical = 5.dp),
|
||||
) {
|
||||
if (text.isNotEmpty()) Text(
|
||||
text,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontSize = 12.sp,
|
||||
color = ink,
|
||||
textAlign = TextAlign.End,
|
||||
)
|
||||
if (graph.size >= 2) {
|
||||
if (text.isNotEmpty()) Spacer(Modifier.height(4.dp))
|
||||
HudGraph(graph, accent, wash)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HudGraph(samples: FloatArray, accent: Color, wash: Float) {
|
||||
Canvas(Modifier.width(148.dp).height(44.dp)) {
|
||||
val maxMs = (samples.maxOrNull() ?: 16.7f).coerceAtLeast(16.7f)
|
||||
val step = size.width / (samples.size - 1)
|
||||
fun yOf(ms: Float) = size.height * (1f - (ms / maxMs).coerceIn(0f, 1f))
|
||||
val fillAlpha = wash * 0.5f
|
||||
val fill = Path().apply {
|
||||
moveTo(0f, size.height)
|
||||
samples.forEachIndexed { i, ms -> lineTo(i * step, yOf(ms)) }
|
||||
lineTo(size.width, size.height)
|
||||
close()
|
||||
}
|
||||
drawPath(
|
||||
fill,
|
||||
Brush.verticalGradient(listOf(HUD_SLOW.copy(alpha = fillAlpha), accent.copy(alpha = fillAlpha * 0.45f))),
|
||||
.background(Color(0x8C000000))
|
||||
.padding(horizontal = 8.dp, vertical = 3.dp),
|
||||
)
|
||||
val guide = yOf(16.7f)
|
||||
drawLine(Color.White.copy(alpha = 0.32f), Offset(0f, guide), Offset(size.width, guide), strokeWidth = 1f)
|
||||
for (i in 0 until samples.lastIndex) {
|
||||
val tone = ((samples[i] - 8f) / 25f).coerceIn(0f, 1f)
|
||||
drawLine(
|
||||
mix(accent, HUD_SLOW, tone).copy(alpha = 0.55f + wash * 0.4f),
|
||||
Offset(i * step, yOf(samples[i])),
|
||||
Offset((i + 1) * step, yOf(samples[i + 1])),
|
||||
strokeWidth = 2.5f,
|
||||
cap = StrokeCap.Round,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun hudInk(color: String): Color = Color(
|
||||
when (color) {
|
||||
"white" -> 0xFFF2F5F8
|
||||
"sky" -> 0xFF66C0F4
|
||||
"amber" -> 0xFFFFB74D
|
||||
"violet" -> 0xFFD0A2FF
|
||||
else -> 0xFF3DDC84
|
||||
}.toInt(),
|
||||
)
|
||||
|
||||
private fun mix(from: Color, to: Color, t: Float): Color {
|
||||
val u = t.coerceIn(0f, 1f)
|
||||
return Color(
|
||||
red = from.red + (to.red - from.red) * u,
|
||||
green = from.green + (to.green - from.green) * u,
|
||||
blue = from.blue + (to.blue - from.blue) * u,
|
||||
alpha = 1f,
|
||||
)
|
||||
}
|
||||
|
||||
private val HUD_SLOW = Color(0xFFFF5C6C.toInt())
|
||||
|
||||
private class LoadStage(@androidx.annotation.StringRes val label: Int, @androidx.annotation.StringRes val detail: Int)
|
||||
|
||||
private fun loadStages(steam: Boolean): List<LoadStage> = if (steam) listOf(
|
||||
|
||||
@@ -195,11 +195,6 @@ class DrawerActions(
|
||||
val isHomeApp: Boolean,
|
||||
val androidApps: List<HomeApp.LaunchableApp>,
|
||||
val hudOn: Boolean,
|
||||
val hudFps: Boolean = true,
|
||||
val hudBattery: Boolean = true,
|
||||
val hudGraph: Boolean = true,
|
||||
val hudColor: String = SessionPrefs.HUD_COLOR_MINT,
|
||||
val hudOpacity: Int = SessionPrefs.HUD_OPACITY_DEFAULT,
|
||||
val frameGen: FrameGen.Mode,
|
||||
val lossless: Lossless.State,
|
||||
val oscMode: String,
|
||||
@@ -227,11 +222,6 @@ class DrawerActions(
|
||||
val secondScreenDisplays: List<SecondScreenDisplay>,
|
||||
val selectedSecondScreenDisplay: Int,
|
||||
val onHud: (Boolean) -> Unit,
|
||||
val onHudFps: (Boolean) -> Unit = {},
|
||||
val onHudBattery: (Boolean) -> Unit = {},
|
||||
val onHudGraph: (Boolean) -> Unit = {},
|
||||
val onHudColor: (String) -> Unit = {},
|
||||
val onHudOpacity: (Int) -> Unit = {},
|
||||
val onFrameGenPick: (FrameGen.Mode) -> Unit,
|
||||
val onImportLossless: () -> Unit,
|
||||
val onKeyboard: () -> Unit,
|
||||
@@ -318,7 +308,7 @@ internal fun SessionDrawer(open: Boolean, requestedPage: SessionDrawerPage, cont
|
||||
val pal = LocalPalette.current
|
||||
val host = rememberMenuHost()
|
||||
val (glasses, glassesSettings) = rememberGlasses()
|
||||
val glassesPages = glassesTabs(glasses) + (GlassesTab.CONTROLLERS to stringResource(R.string.vr_controllers_tab))
|
||||
val glassesPages = glassesTabs(glasses)
|
||||
var selectedGlassesTab by rememberSaveable { mutableStateOf(GlassesTab.DISPLAY) }
|
||||
val glassesTab = selectedGlassesTab.takeIf { key -> glassesPages.any { it.first == key } } ?: GlassesTab.DISPLAY
|
||||
var androidAppsExpanded by rememberSaveable { mutableStateOf(false) }
|
||||
@@ -472,30 +462,6 @@ internal fun SessionDrawer(open: Boolean, requestedPage: SessionDrawerPage, cont
|
||||
SettingsGroup(stringResource(R.string.drawer_page_display)) {
|
||||
ToggleRow(host, "hud", stringResource(R.string.drawer_hud), null, a.hudOn,
|
||||
chipModifier = focus.track(page, "hud"), onChange = a.onHud)
|
||||
if (a.hudOn) {
|
||||
ToggleRow(host, "hud-fps", stringResource(R.string.drawer_hud_fps), null, a.hudFps,
|
||||
chipModifier = focus.track(page, "hud-fps"), onChange = a.onHudFps)
|
||||
ToggleRow(host, "hud-battery", stringResource(R.string.drawer_hud_battery), null, a.hudBattery,
|
||||
chipModifier = focus.track(page, "hud-battery"), onChange = a.onHudBattery)
|
||||
ToggleRow(host, "hud-graph", stringResource(R.string.drawer_hud_graph), null, a.hudGraph,
|
||||
chipModifier = focus.track(page, "hud-graph"), onChange = a.onHudGraph)
|
||||
ChoiceRow(
|
||||
host, "hud-color", stringResource(R.string.drawer_hud_color), null,
|
||||
listOf(
|
||||
SessionPrefs.HUD_COLOR_MINT to stringResource(R.string.drawer_hud_color_mint),
|
||||
"white" to stringResource(R.string.drawer_hud_color_white),
|
||||
"sky" to stringResource(R.string.drawer_hud_color_sky),
|
||||
"amber" to stringResource(R.string.drawer_hud_color_amber),
|
||||
"violet" to stringResource(R.string.drawer_hud_color_violet),
|
||||
),
|
||||
a.hudColor, chipModifier = focus.track(page, "hud-color"), onPick = a.onHudColor,
|
||||
)
|
||||
SliderRow(
|
||||
stringResource(R.string.drawer_hud_opacity), null, a.hudOpacity,
|
||||
SessionPrefs.HUD_OPACITY_RANGE, step = 10, format = { "$it%" },
|
||||
modifier = focus.track(page, "hud-opacity"), onChange = a.onHudOpacity,
|
||||
)
|
||||
}
|
||||
val fgOpen = host.open == "fg"
|
||||
val fgLabel = FrameGen.label(LocalContext.current, a.frameGen)
|
||||
SettingsRow(stringResource(R.string.frame_gen_title), null, highlighted = fgOpen) {
|
||||
@@ -591,9 +557,7 @@ internal fun SessionDrawer(open: Boolean, requestedPage: SessionDrawerPage, cont
|
||||
SessionDrawerPage.SECOND_SCREEN -> {
|
||||
SecondScreenGroup(host, a) { key -> focus.track(page, key) }
|
||||
}
|
||||
SessionDrawerPage.GLASSES -> if (glassesTab == GlassesTab.CONTROLLERS) {
|
||||
VrControllerGroups(host) { key -> focus.track(page, key) }
|
||||
} else {
|
||||
SessionDrawerPage.GLASSES -> {
|
||||
GlassesGroups(host, glasses, glassesSettings, { key -> focus.track(page, key) }, onRecenter = a.onGlassesRecenter,
|
||||
tab = glassesTab, outputActive = a.glassesOutput, steamVrRunning = a.steamVr == SteamVr.Status.RUNNING,
|
||||
steamVr = { SteamVrRow(a) })
|
||||
|
||||
@@ -115,14 +115,13 @@ internal fun SteamHome(s: FrontEndState, a: FrontEndActions, modifier: Modifier)
|
||||
drop = drop.takeIf { headset },
|
||||
overlay = { lifted ->
|
||||
if (headset) DropSheet(
|
||||
drop, glassesTabs(glasses) + (GlassesTab.CONTROLLERS to stringResource(R.string.vr_controllers_tab)), covered = lifted,
|
||||
drop, glassesTabs(glasses), covered = lifted,
|
||||
button = { enabled ->
|
||||
PrimaryButton(stringResource(R.string.games_play_steamvr), enabled = !s.busy && glasses.usable, large = true, icon = Icons.Filled.PlayArrow, fill = true,
|
||||
modifier = Modifier.fillMaxWidth().testTag("play-steamvr")) { if (enabled) { drop.open = false; a.onPlaySteamVr() } }
|
||||
},
|
||||
) { tab ->
|
||||
if (tab == GlassesTab.CONTROLLERS) VrControllerGroups(rememberMenuHost())
|
||||
else GlassesGroups(rememberMenuHost(), glasses, glassesSettings,
|
||||
GlassesGroups(rememberMenuHost(), glasses, glassesSettings,
|
||||
onRecenter = GlassesMenuMirror::recenter, inSession = false, tab = tab)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -51,6 +51,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import coil.compose.AsyncImage
|
||||
import com.droiddeck.launcher.R
|
||||
import com.droiddeck.launcher.stores.CatalogItem
|
||||
import com.droiddeck.launcher.stores.Store
|
||||
import com.droiddeck.launcher.stores.StoreAchievements
|
||||
import com.droiddeck.launcher.stores.StoresState
|
||||
@@ -67,7 +68,7 @@ internal fun StoreGameDetail(store: Store, key: String, s: FrontEndState, a: Fro
|
||||
val narrow = LocalNarrowPane.current
|
||||
val stacked = narrow || LocalConfiguration.current.screenWidthDp < 700
|
||||
val id = key.substringAfter(':')
|
||||
val item = StoresState.detailItem(store, id)
|
||||
val item = (StoresState.library[store].orEmpty() + StoresState.shelves[store]?.all.orEmpty()).firstOrNull { it.id == id }
|
||||
val installed = StoresState.installedGame(store, id)
|
||||
val rawDownload = StoresState.download("${store.id}:$id")
|
||||
val download = rawDownload?.takeIf { it.isActive }
|
||||
@@ -92,14 +93,11 @@ internal fun StoreGameDetail(store: Store, key: String, s: FrontEndState, a: Fro
|
||||
HeroScores(scores, Modifier.align(Alignment.BottomEnd).padding(end = 16.dp, bottom = 18.dp))
|
||||
}
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.align(Alignment.BottomStart).padding(horizontal = 22.dp, vertical = 18.dp)) {
|
||||
val passName = item?.includedWith.orEmpty()
|
||||
val state = when {
|
||||
installed != null -> stringResource(R.string.stores_eyebrow_installed) + " · " + com.droiddeck.launcher.stores.StoreInstallRoot.labelFor(ctx, installed.folder)
|
||||
download != null && download.location.isNotBlank() -> stringResource(R.string.stores_eyebrow_library) + " · " + download.location
|
||||
failedDownload != null && failedDownload.location.isNotBlank() -> stringResource(R.string.stores_eyebrow_library) + " · " + failedDownload.location
|
||||
item?.owned == true -> stringResource(R.string.stores_eyebrow_library)
|
||||
item?.subscription == true -> passName.ifBlank { "PC Game Pass" }
|
||||
passName.isNotBlank() -> "Available with $passName"
|
||||
item?.subscription == true -> "PC Game Pass"
|
||||
item?.accessible == true -> stringResource(R.string.stores_eyebrow_library)
|
||||
else -> stringResource(R.string.stores_eyebrow_store)
|
||||
}
|
||||
@@ -155,25 +153,14 @@ internal fun StoreGameDetail(store: Store, key: String, s: FrontEndState, a: Fro
|
||||
com.droiddeck.launcher.stores.download.DownloadQueue.clear(ctx, failedDownload.key)
|
||||
}
|
||||
}
|
||||
item?.installable == true && StoresState.isUnfinished(item) -> {
|
||||
item?.accessible == true && StoresState.isUnfinished(item) -> {
|
||||
InstallButton(stringResource(R.string.stores_resume_install), leadOn) { StoresState.requestInstall(ctx, item) }
|
||||
ConfirmButton(stringResource(R.string.stores_dl_clear), stringResource(R.string.stores_dl_cancel_confirm), compact = true) { StoresState.clearUnfinished(ctx, item) }
|
||||
}
|
||||
item?.installable == true -> {
|
||||
InstallButton(
|
||||
if (item.sizeBytes > 0) stringResource(R.string.stores_install_size, formatBytes(item.sizeBytes)) else stringResource(R.string.stores_install),
|
||||
leadOn,
|
||||
) { StoresState.requestInstall(ctx, item) }
|
||||
if (!item.accessible) {
|
||||
SecondaryButton(
|
||||
when {
|
||||
item.isFree -> stringResource(R.string.stores_get_free)
|
||||
item.hasPrice && item.finalPrice.isNotBlank() -> stringResource(R.string.stores_buy_price, item.finalPrice)
|
||||
else -> stringResource(R.string.stores_view_on, store.shortLabel)
|
||||
},
|
||||
) { openStoreUrl(ctx, item) }
|
||||
}
|
||||
}
|
||||
item?.accessible == true -> InstallButton(
|
||||
if (item.sizeBytes > 0) stringResource(R.string.stores_install_size, formatBytes(item.sizeBytes)) else stringResource(R.string.stores_install),
|
||||
leadOn,
|
||||
) { StoresState.requestInstall(ctx, item) }
|
||||
item != null && item.isFree -> PrimaryButton(stringResource(R.string.stores_get_free), main = true, modifier = leadOn) { openStoreUrl(ctx, item) }
|
||||
item != null -> PrimaryButton(if (item.hasPrice && item.finalPrice.isNotBlank()) stringResource(R.string.stores_buy_price, item.finalPrice) else stringResource(R.string.stores_view_on, store.shortLabel), main = true, modifier = leadOn) { openStoreUrl(ctx, item) }
|
||||
}
|
||||
@@ -184,7 +171,7 @@ internal fun StoreGameDetail(store: Store, key: String, s: FrontEndState, a: Fro
|
||||
}
|
||||
if (StoresMotion.heroLands) hero() else Rise(1) { hero() }
|
||||
if (scores != null) Rise(2) {
|
||||
val listed = (StoresState.found[store].orEmpty() + StoresState.shelves[store]?.all.orEmpty()).firstOrNull { it.id == id }
|
||||
val listed = StoresState.shelves[store]?.all.orEmpty().firstOrNull { it.id == id }
|
||||
val developer = item?.developer?.ifBlank { null } ?: listed?.developer.orEmpty()
|
||||
val publisher = item?.publisher?.ifBlank { null } ?: listed?.publisher.orEmpty()
|
||||
val releaseDate = item?.releaseDate?.ifBlank { null } ?: listed?.releaseDate.orEmpty()
|
||||
|
||||
@@ -65,7 +65,7 @@ internal fun InstallWhereDialog(item: CatalogItem, onDismiss: () -> Unit) {
|
||||
)
|
||||
Text(
|
||||
stringResource(R.string.common_cancel), fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = colors.onSurfaceVariant,
|
||||
modifier = Modifier.clip(Shape12).clickable(role = Role.Button, onClick = close).controllerConfirm(onClick = close).padding(horizontal = 10.dp, vertical = 6.dp),
|
||||
modifier = Modifier.clip(Shape12).clickable(role = Role.Button, onClick = close).padding(horizontal = 10.dp, vertical = 6.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,12 +201,12 @@ internal fun StoresPage(s: FrontEndState, a: FrontEndActions, modifier: Modifier
|
||||
val colors = MaterialTheme.colorScheme
|
||||
val narrow = LocalNarrowPane.current
|
||||
val padH = if (narrow) 14.dp else 22.dp
|
||||
var chip by rememberSaveable { mutableStateOf(Store.GOG.id) }
|
||||
val tabs = remember {
|
||||
androidx.compose.runtime.mutableStateListOf<StoreTab>().apply {
|
||||
addAll(arrangeStoreTabs(SessionPrefs.storesTabOrder(ctx), SessionPrefs.storesTabHidden(ctx)))
|
||||
}
|
||||
}
|
||||
var chip by rememberSaveable { mutableStateOf(tabs.firstOrNull { it.shown }?.store?.id ?: Store.GOG.id) }
|
||||
var arranging by remember { mutableStateOf(false) }
|
||||
val shownStores = tabs.filter { it.shown }.map { it.store }
|
||||
fun commitTabs() {
|
||||
@@ -638,9 +638,8 @@ private fun StoreDot(store: Store, signedIn: Boolean) {
|
||||
Box(
|
||||
Modifier.size(12.dp)
|
||||
.onGloballyPositioned { StoresMotion.dots[store] = it.boundsInRoot() }
|
||||
.pulseRing(StoresMotion.pulses[store] ?: 0, c.dot)
|
||||
.background(c.dot.copy(alpha = if (signedIn) 1f else 0.35f), CircleShape),
|
||||
)
|
||||
.pulseRing(StoresMotion.pulses[store] ?: 0, c.dot),
|
||||
) { Box(Modifier.matchParentSize().clip(CircleShape).background(c.dot).alpha(if (signedIn) 1f else 0.35f)) }
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -69,7 +69,7 @@ fun TouchGestureDialog(game: String?, onDismiss: () -> Unit, onSaved: () -> Unit
|
||||
val actions = mouse + ACTION_SHOW_KEYBOARD + GestureKeys.codes.keys.map { "key_$it" } + ACTION_KEY_TILDE
|
||||
val p = LocalPalette.current
|
||||
|
||||
GestureDialogFrame(onDismiss,
|
||||
GestureDialogFrame(stringResource(R.string.gesture_settings), stringResource(R.string.gesture_editor_subtitle), onDismiss,
|
||||
footer = {
|
||||
Row(Modifier.fillMaxWidth().padding(horizontal = 20.dp, vertical = 12.dp), horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically) {
|
||||
TextButton(onClick = onDismiss) { Text(stringResource(R.string.gesture_cancel)) }
|
||||
@@ -83,6 +83,15 @@ fun TouchGestureDialog(game: String?, onDismiss: () -> Unit, onSaved: () -> Unit
|
||||
}, modifier = Modifier.testTag("gesture-save")) { Text(stringResource(R.string.gesture_save_changes)) }
|
||||
}
|
||||
}) {
|
||||
Column(Modifier.padding(horizontal = 20.dp)) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
GestureFilterChip(!forGame, onClick = { forGame = false }, label = { Text(stringResource(R.string.gesture_defaults)) }, modifier = Modifier.testTag("gesture-scope-default"))
|
||||
if (targetGame != null) GestureFilterChip(forGame, onClick = { forGame = true }, label = { Text(stringResource(R.string.gesture_game)) }, modifier = Modifier.testTag("gesture-scope-game"))
|
||||
if (forGame && gameDraft != null) TextButton(onClick = { gameDraft = null }, modifier = Modifier.testTag("gesture-inherit")) { Text(stringResource(R.string.gesture_use_defaults)) }
|
||||
}
|
||||
Text(stringResource(if (!forGame) R.string.gesture_scope_default else if (gameDraft == null) R.string.gesture_inheriting else R.string.gesture_scope_game),
|
||||
color = p.onSurfaceVariant, style = MaterialTheme.typography.bodySmall, modifier = Modifier.padding(bottom = 12.dp))
|
||||
}
|
||||
ScrollableTabRow(tab, edgePadding = 12.dp, containerColor = p.surface, divider = { HorizontalDivider(color = p.line) }) {
|
||||
tabs.forEachIndexed { index, title ->
|
||||
Tab(tab == index, onClick = { tab = index }, text = { Text(stringResource(title)) }, selectedContentColor = p.onBackground,
|
||||
@@ -91,16 +100,9 @@ fun TouchGestureDialog(game: String?, onDismiss: () -> Unit, onSaved: () -> Unit
|
||||
}
|
||||
key(tab) {
|
||||
Column(Modifier.weight(1f).fillMaxWidth().verticalScroll(rememberScrollState()).padding(20.dp), verticalArrangement = Arrangement.spacedBy(14.dp)) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
GestureFilterChip(!forGame, onClick = { forGame = false }, label = { Text(stringResource(R.string.gesture_defaults)) }, modifier = Modifier.testTag("gesture-scope-default"))
|
||||
if (targetGame != null) GestureFilterChip(forGame, onClick = { forGame = true }, label = { Text(stringResource(R.string.gesture_game)) }, modifier = Modifier.testTag("gesture-scope-game"))
|
||||
}
|
||||
if (forGame && gameDraft != null) TextButton(onClick = { gameDraft = null }, modifier = Modifier.testTag("gesture-inherit")) { Text(stringResource(R.string.gesture_use_defaults)) }
|
||||
}
|
||||
when (tab) {
|
||||
0 -> {
|
||||
GestureSection(R.string.gesture_tab_one)
|
||||
GestureSection(R.string.gesture_tab_one, R.string.gesture_one_hint)
|
||||
GestureCard(R.string.gesture_tap, config.tapEnabled, { change(config.copy(tapEnabled = it)) }) {
|
||||
GestureActionField("tap", R.string.gesture_tap, config.tapAction, actions) { change(config.copy(tapAction = it)) }
|
||||
}
|
||||
@@ -116,7 +118,7 @@ fun TouchGestureDialog(game: String?, onDismiss: () -> Unit, onSaved: () -> Unit
|
||||
}
|
||||
}
|
||||
1 -> {
|
||||
GestureSection(R.string.gesture_tab_two)
|
||||
GestureSection(R.string.gesture_tab_two, R.string.gesture_two_hint)
|
||||
GestureCard(R.string.gesture_two_tap, config.twoFingerTapEnabled, { change(config.copy(twoFingerTapEnabled = it)) }) {
|
||||
GestureActionField("two-tap", R.string.gesture_two_tap, config.twoFingerTapAction, actions) { change(config.copy(twoFingerTapAction = it)) }
|
||||
}
|
||||
@@ -132,7 +134,7 @@ fun TouchGestureDialog(game: String?, onDismiss: () -> Unit, onSaved: () -> Unit
|
||||
}
|
||||
}
|
||||
2 -> {
|
||||
GestureSection(R.string.gesture_tab_three)
|
||||
GestureSection(R.string.gesture_tab_three, R.string.gesture_three_hint)
|
||||
GestureCard(R.string.gesture_three_tap, config.threeFingerTapEnabled, { change(config.copy(threeFingerTapEnabled = it)) }) {
|
||||
GestureActionField("three-tap", R.string.gesture_three_tap, config.threeFingerTapAction, actions) { change(config.copy(threeFingerTapAction = it)) }
|
||||
}
|
||||
@@ -145,13 +147,16 @@ fun TouchGestureDialog(game: String?, onDismiss: () -> Unit, onSaved: () -> Unit
|
||||
}
|
||||
}
|
||||
3 -> {
|
||||
GestureSection(R.string.gesture_tab_options)
|
||||
GestureSection(R.string.gesture_tab_options, R.string.gesture_options_hint)
|
||||
GestureSettingsCard(R.string.gesture_movement) {
|
||||
Text(stringResource(R.string.gesture_movement_hint), color = p.onSurfaceVariant, style = MaterialTheme.typography.bodyMedium)
|
||||
GestureChoiceChips(config.mouseDragMovementMode, listOf(MOUSE_DRAG_MOVEMENT_DIRECT to stringResource(R.string.gesture_direct), MOUSE_DRAG_MOVEMENT_RELATIVE to stringResource(R.string.gesture_relative))) { change(config.copy(mouseDragMovementMode = it)) }
|
||||
Text(stringResource(R.string.gesture_middle_relative_hint), color = p.onSurfaceVariant, style = MaterialTheme.typography.bodySmall)
|
||||
}
|
||||
GestureSettingsCard(R.string.gesture_recognition) {
|
||||
Text(stringResource(R.string.gesture_threshold, config.gestureThreshold), style = MaterialTheme.typography.bodyMedium)
|
||||
Slider(config.gestureThreshold.toFloat(), { change(config.copy(gestureThreshold = it.toInt())) }, valueRange = 10f..100f, steps = 17)
|
||||
Text(stringResource(R.string.gesture_threshold_hint), color = p.onSurfaceVariant, style = MaterialTheme.typography.bodySmall)
|
||||
}
|
||||
GestureSettingsCard(R.string.gesture_feedback) {
|
||||
GestureToggle(R.string.gesture_debug, config.showGestureDebugOverlay) { change(config.copy(showGestureDebugOverlay = it)) }
|
||||
@@ -165,7 +170,7 @@ fun TouchGestureDialog(game: String?, onDismiss: () -> Unit, onSaved: () -> Unit
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun GestureDialogFrame(onDismiss: () -> Unit,
|
||||
private fun GestureDialogFrame(title: String, subtitle: String, onDismiss: () -> Unit,
|
||||
footer: @Composable () -> Unit = {}, content: @Composable ColumnScope.() -> Unit) {
|
||||
val p = LocalPalette.current
|
||||
// The app window does not opt into RTL, so these dialogs follow their resource locale explicitly.
|
||||
@@ -176,8 +181,15 @@ private fun GestureDialogFrame(onDismiss: () -> Unit,
|
||||
Surface(Modifier.widthIn(max = 1040.dp).fillMaxWidth().fillMaxHeight(), shape = RoundedCornerShape(22.dp), color = p.background,
|
||||
contentColor = p.onBackground, border = BorderStroke(1.dp, p.line2)) {
|
||||
Column {
|
||||
Row(Modifier.fillMaxWidth().padding(end = 4.dp), horizontalArrangement = Arrangement.End) {
|
||||
IconButton(onClick = onDismiss, modifier = Modifier.size(40.dp)) { Icon(Icons.Default.Close, stringResource(R.string.gesture_close)) }
|
||||
Row(Modifier.fillMaxWidth().padding(start = 20.dp, end = 8.dp, top = 14.dp, bottom = 12.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
Surface(shape = RoundedCornerShape(12.dp), color = p.primary.copy(alpha = 0.14f)) {
|
||||
Icon(Icons.Default.TouchApp, null, tint = p.primary, modifier = Modifier.padding(10.dp).size(24.dp))
|
||||
}
|
||||
Column(Modifier.weight(1f).padding(horizontal = 14.dp), verticalArrangement = Arrangement.spacedBy(3.dp)) {
|
||||
Text(title, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.SemiBold)
|
||||
Text(subtitle, style = MaterialTheme.typography.bodySmall, color = p.onSurfaceVariant)
|
||||
}
|
||||
IconButton(onClick = onDismiss) { Icon(Icons.Default.Close, stringResource(R.string.gesture_close)) }
|
||||
}
|
||||
content()
|
||||
HorizontalDivider(color = p.line)
|
||||
@@ -190,8 +202,11 @@ private fun GestureDialogFrame(onDismiss: () -> Unit,
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun GestureSection(@StringRes title: Int) {
|
||||
Text(stringResource(title), style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold)
|
||||
private fun GestureSection(@StringRes title: Int, @StringRes hint: Int) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
Text(stringResource(title), style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold)
|
||||
Text(stringResource(hint), style = MaterialTheme.typography.bodyMedium, color = LocalPalette.current.onSurfaceVariant)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -227,18 +242,18 @@ private fun GestureActionField(id: String, @StringRes gesture: Int, selected: St
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowForward, null, modifier = Modifier.size(19.dp), tint = p.onSurfaceVariant)
|
||||
}
|
||||
}
|
||||
if (open) GestureActionPicker(choices, selected, onDismiss = { open = false }) { open = false; onChoose(it) }
|
||||
if (open) GestureActionPicker(choices, selected, stringResource(gesture), onDismiss = { open = false }) { open = false; onChoose(it) }
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun GestureActionPicker(choices: List<String>, selected: String, onDismiss: () -> Unit, onChoose: (String) -> Unit) {
|
||||
internal fun GestureActionPicker(choices: List<String>, selected: String, gesture: String, onDismiss: () -> Unit, onChoose: (String) -> Unit) {
|
||||
var query by rememberSaveable { mutableStateOf("") }
|
||||
var category by remember { mutableStateOf<GestureActionGroup?>(null) }
|
||||
val catalog = choices.distinct().map { GestureAction(it, gestureActionTitle(it), gestureActionGroup(it)) }
|
||||
val groups = GestureActionGroup.entries.filter { group -> catalog.any { it.group == group } }
|
||||
val shown = filterGestureActions(catalog, query, category)
|
||||
val p = LocalPalette.current
|
||||
GestureDialogFrame(onDismiss) {
|
||||
GestureDialogFrame(stringResource(R.string.gesture_action), stringResource(R.string.gesture_action_for, gesture), onDismiss) {
|
||||
OutlinedTextField(query, onValueChange = { query = it }, modifier = Modifier.fillMaxWidth().padding(horizontal = 20.dp).testTag("gesture-action-search"),
|
||||
singleLine = true, shape = RoundedCornerShape(12.dp), placeholder = { Text(stringResource(R.string.gesture_search_actions)) },
|
||||
leadingIcon = { Icon(Icons.Default.Search, null) }, trailingIcon = {
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
<string name="gesture_click_once">النقر مرة واحدة</string>
|
||||
<string name="gesture_hold_delay">مهلة الضغط المستمر</string>
|
||||
<string name="gesture_double_delay">الفاصل بين النقرتين</string>
|
||||
<string name="gesture_scope_game">ينطبق على اللعبة الحالية</string>
|
||||
<string name="gesture_scope_default">الإعدادات الافتراضية لجميع الألعاب</string>
|
||||
<string name="gesture_reset">استعادة إعدادات الإيماءات الأصلية</string>
|
||||
<string name="gesture_cancel">إلغاء</string>
|
||||
<string name="gesture_action">اختيار إجراء</string>
|
||||
@@ -35,16 +37,23 @@
|
||||
<string name="gesture_scroll">عجلة الفأرة</string>
|
||||
<string name="gesture_threshold">عتبة الإيماءات: %1$d بكسل</string>
|
||||
<string name="gesture_debug">إظهار الإيماءة المكتشفة</string>
|
||||
<string name="gesture_editor_subtitle">عناصر تحكم الفأرة ولوحة المفاتيح مرتبة حسب الإيماءة</string>
|
||||
<string name="gesture_tab_one">إصبع واحد</string>
|
||||
<string name="gesture_tab_two">إصبعان</string>
|
||||
<string name="gesture_tab_three">ثلاثة أصابع</string>
|
||||
<string name="gesture_tab_options">خيارات</string>
|
||||
<string name="gesture_one_hint">اختر إجراء النقر أو السحب أو الضغط المستمر.</string>
|
||||
<string name="gesture_two_hint">اضبط تحريك الكاميرا والتكبير والنقرات الثانوية.</string>
|
||||
<string name="gesture_three_hint">اجعل الاختصارات الإضافية في متناولك.</string>
|
||||
<string name="gesture_options_hint">اضبط الحركة والتعرّف والاستجابة المرئية.</string>
|
||||
<string name="gesture_defaults">الافتراضية</string>
|
||||
<string name="gesture_game">هذه اللعبة</string>
|
||||
<string name="gesture_use_defaults">استخدام الإعدادات الافتراضية</string>
|
||||
<string name="gesture_inheriting">تُستخدم الإعدادات الافتراضية. يؤدي التعديل إلى إنشاء إعدادات خاصة باللعبة.</string>
|
||||
<string name="gesture_save_changes">حفظ التغييرات</string>
|
||||
<string name="gesture_close">إغلاق</string>
|
||||
<string name="gesture_action_label">الإجراء</string>
|
||||
<string name="gesture_action_for">لـ %1$s</string>
|
||||
<string name="gesture_search_actions">البحث عن إجراءات الفأرة أو مفاتيح لوحة المفاتيح</string>
|
||||
<string name="gesture_clear_search">مسح البحث</string>
|
||||
<string name="gesture_all_actions">الكل</string>
|
||||
@@ -59,9 +68,12 @@
|
||||
<string name="gesture_group_numpad">لوحة الأرقام</string>
|
||||
<string name="gesture_group_symbols">الرموز</string>
|
||||
<string name="gesture_movement">حركة الفأرة</string>
|
||||
<string name="gesture_movement_hint">يتبع السحب المباشر موضع اللمس. ويتبع السحب النسبي حركة الإصبع، وهو مفيد عندما تعيد اللعبة الفأرة إلى المنتصف.</string>
|
||||
<string name="gesture_middle_relative_hint">يتبع تحريك العرض بالزر الأوسط حركة الإصبع دائمًا لتجنّب قفزات المؤشر.</string>
|
||||
<string name="gesture_direct">مباشر</string>
|
||||
<string name="gesture_relative">نسبي</string>
|
||||
<string name="gesture_recognition">التعرّف على الإيماءات</string>
|
||||
<string name="gesture_threshold_hint">تحرّك هذه المسافة قبل أن تتحول إيماءة بإصبعين أو ثلاثة إلى تحريك العرض أو التكبير.</string>
|
||||
<string name="gesture_feedback">الاستجابة المرئية</string>
|
||||
<string name="gesture_mouse_behavior">سلوك زر الفأرة</string>
|
||||
<string name="gesture_ms">%1$d مللي ثانية</string>
|
||||
|
||||
@@ -590,16 +590,6 @@
|
||||
<string name="drawer_prev_page">الصفحة السابقة</string>
|
||||
<string name="drawer_next_page">الصفحة التالية</string>
|
||||
<string name="drawer_hud">واجهة الأداء</string>
|
||||
<string name="drawer_hud_fps">معدل الإطارات</string>
|
||||
<string name="drawer_hud_battery">درجة حرارة البطارية</string>
|
||||
<string name="drawer_hud_graph">مخطط الأداء</string>
|
||||
<string name="drawer_hud_color">اللون</string>
|
||||
<string name="drawer_hud_color_mint">نعناعي</string>
|
||||
<string name="drawer_hud_color_white">أبيض</string>
|
||||
<string name="drawer_hud_color_sky">سماوي</string>
|
||||
<string name="drawer_hud_color_amber">كهرماني</string>
|
||||
<string name="drawer_hud_color_violet">بنفسجي</string>
|
||||
<string name="drawer_hud_opacity">العتامة</string>
|
||||
<string name="drawer_scaling">وضع التحجيم</string>
|
||||
<string name="drawer_scaling_sharpness">حدة التحجيم</string>
|
||||
<string name="drawer_effects">مؤثرات الشاشة</string>
|
||||
@@ -2120,9 +2110,6 @@
|
||||
<string name="cloud_last_sync">آخر مزامنة %1$s</string>
|
||||
<string name="cloud_never">لم تتم المزامنة بعد</string>
|
||||
<string name="cloud_done">%1$d ملفات</string>
|
||||
<string name="cloud_up_to_date">محدَّث بالفعل</string>
|
||||
<string name="cloud_signed_out">سجّل الدخول إلى المتجر لمزامنة الحفظ</string>
|
||||
<string name="cloud_not_synced">لم تتم مزامنة الحفظ</string>
|
||||
<string name="stores_eyebrow_installed">مثبَّتة</string>
|
||||
<string name="stores_eyebrow_library">في مكتبتك</string>
|
||||
<string name="stores_eyebrow_store">متجر</string>
|
||||
@@ -2434,7 +2421,7 @@
|
||||
<string name="touch_done">تم</string>
|
||||
<string name="touch_save_failed">تعذّر حفظ تخطيط اللمس</string>
|
||||
<string name="touch_saved">تم حفظ تخطيط اللمس في Steam</string>
|
||||
<string name="touch_app_id">التطبيق %1$s</string>
|
||||
<string name="touch_app_id">التطبيق %1$d</string>
|
||||
<string name="touch_label_hint">التسمية (اختياري)</string>
|
||||
<string name="touch_icons_missing">لم يتم العثور على أيقونات Steam</string>
|
||||
<plurals name="touch_icons_found">
|
||||
@@ -2533,15 +2520,6 @@
|
||||
<string name="glasses_vr_fov">مجال رؤية SteamVR</string>
|
||||
<string name="glasses_vr_fov_hint">القيمة الأقل تقرّب SteamVR وتملأ النظارة؛ والأعلى تُظهر المزيد مما حولك.</string>
|
||||
<string name="glasses_playing">جارٍ اللعب على %1$s</string>
|
||||
<string name="glasses_phone_recenter">إعادة التوسيط</string>
|
||||
<string name="glasses_phone_steamvr_menu">قائمة SteamVR</string>
|
||||
<string name="glasses_phone_switch_sticks">العصي: اليدان ⇄ اللعبة</string>
|
||||
<string name="glasses_phone_controllers">وحدات التحكم: %1$s</string>
|
||||
<string name="glasses_phone_keyboard">لوحة المفاتيح</string>
|
||||
<string name="glasses_phone_session_menu">قائمة الجلسة</string>
|
||||
<string name="glasses_phone_stop_steamvr">إيقاف SteamVR</string>
|
||||
<string name="glasses_phone_brightness">السطوع %1$d / %2$d</string>
|
||||
<string name="glasses_phone_battery">البطارية %1$d%% · %2$d °C</string>
|
||||
<string name="glasses_calibration">معايرة التتبّع</string>
|
||||
<string name="glasses_calibration_hint">يقلّل الانجراف ويضبط المرجع الأفقي.</string>
|
||||
<string name="glasses_calibration_saved_hint">تُطبَّق المعايرة المحفوظة على هذه النظارة.</string>
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
<string name="gesture_click_once">Hacer clic una vez</string>
|
||||
<string name="gesture_hold_delay">Tiempo de pulsación sostenida</string>
|
||||
<string name="gesture_double_delay">Intervalo de doble toque</string>
|
||||
<string name="gesture_scope_game">Se aplica al juego actual</string>
|
||||
<string name="gesture_scope_default">Ajustes predeterminados para todos los juegos</string>
|
||||
<string name="gesture_reset">Restablecer los ajustes originales de gestos</string>
|
||||
<string name="gesture_cancel">Cancelar</string>
|
||||
<string name="gesture_action">Elegir acción</string>
|
||||
@@ -35,16 +37,23 @@
|
||||
<string name="gesture_scroll">Rueda del ratón</string>
|
||||
<string name="gesture_threshold">Umbral de gestos: %1$d px</string>
|
||||
<string name="gesture_debug">Mostrar gesto reconocido</string>
|
||||
<string name="gesture_editor_subtitle">Controles de ratón y teclado organizados por gesto</string>
|
||||
<string name="gesture_tab_one">Un dedo</string>
|
||||
<string name="gesture_tab_two">Dos dedos</string>
|
||||
<string name="gesture_tab_three">Tres dedos</string>
|
||||
<string name="gesture_tab_options">Opciones</string>
|
||||
<string name="gesture_one_hint">Elige qué debe hacer un toque, arrastre o pulsación sostenida.</string>
|
||||
<string name="gesture_two_hint">Configura el movimiento de cámara, el zoom y los clics secundarios.</string>
|
||||
<string name="gesture_three_hint">Ten más accesos directos al alcance de la mano.</string>
|
||||
<string name="gesture_options_hint">Ajusta el movimiento, el reconocimiento y la respuesta visual.</string>
|
||||
<string name="gesture_defaults">Predeterminados</string>
|
||||
<string name="gesture_game">Este juego</string>
|
||||
<string name="gesture_use_defaults">Usar valores predeterminados</string>
|
||||
<string name="gesture_inheriting">Se usan los ajustes predeterminados. Al editarlos, se crean ajustes específicos del juego.</string>
|
||||
<string name="gesture_save_changes">Guardar cambios</string>
|
||||
<string name="gesture_close">Cerrar</string>
|
||||
<string name="gesture_action_label">ACCIÓN</string>
|
||||
<string name="gesture_action_for">Para %1$s</string>
|
||||
<string name="gesture_search_actions">Buscar acciones de ratón o teclas del teclado</string>
|
||||
<string name="gesture_clear_search">Borrar búsqueda</string>
|
||||
<string name="gesture_all_actions">Todas</string>
|
||||
@@ -59,9 +68,12 @@
|
||||
<string name="gesture_group_numpad">Teclado numérico</string>
|
||||
<string name="gesture_group_symbols">Símbolos</string>
|
||||
<string name="gesture_movement">Movimiento del ratón</string>
|
||||
<string name="gesture_movement_hint">El arrastre directo sigue la posición del toque. El relativo sigue el movimiento del dedo y resulta útil cuando un juego centra el ratón.</string>
|
||||
<string name="gesture_middle_relative_hint">El desplazamiento con el botón central siempre sigue el movimiento del dedo para evitar saltos del cursor.</string>
|
||||
<string name="gesture_direct">Directo</string>
|
||||
<string name="gesture_relative">Relativo</string>
|
||||
<string name="gesture_recognition">Reconocimiento de gestos</string>
|
||||
<string name="gesture_threshold_hint">Recorre esta distancia antes de que un gesto con dos o tres dedos se convierta en desplazamiento o zoom.</string>
|
||||
<string name="gesture_feedback">Respuesta visual</string>
|
||||
<string name="gesture_mouse_behavior">Comportamiento del botón del ratón</string>
|
||||
<string name="gesture_ms">%1$d ms</string>
|
||||
|
||||
@@ -577,16 +577,6 @@
|
||||
<string name="drawer_prev_page">Página anterior</string>
|
||||
<string name="drawer_next_page">Página siguiente</string>
|
||||
<string name="drawer_hud">HUD de rendimiento</string>
|
||||
<string name="drawer_hud_fps">Tasa de fotogramas</string>
|
||||
<string name="drawer_hud_battery">Temperatura de la batería</string>
|
||||
<string name="drawer_hud_graph">Gráfico de rendimiento</string>
|
||||
<string name="drawer_hud_color">Color</string>
|
||||
<string name="drawer_hud_color_mint">Menta</string>
|
||||
<string name="drawer_hud_color_white">Blanco</string>
|
||||
<string name="drawer_hud_color_sky">Celeste</string>
|
||||
<string name="drawer_hud_color_amber">Ámbar</string>
|
||||
<string name="drawer_hud_color_violet">Violeta</string>
|
||||
<string name="drawer_hud_opacity">Opacidad</string>
|
||||
<string name="drawer_scaling">Modo de escalado</string>
|
||||
<string name="drawer_scaling_sharpness">Nitidez del escalado</string>
|
||||
<string name="drawer_effects">Efectos de pantalla</string>
|
||||
@@ -2063,9 +2053,6 @@
|
||||
<string name="cloud_last_sync">Última sincronización %1$s</string>
|
||||
<string name="cloud_never">Aún sin sincronizar</string>
|
||||
<string name="cloud_done">%1$d archivos</string>
|
||||
<string name="cloud_up_to_date">Ya está al día</string>
|
||||
<string name="cloud_signed_out">Inicia sesión en la tienda para sincronizar las partidas</string>
|
||||
<string name="cloud_not_synced">No se sincronizaron las partidas</string>
|
||||
<string name="stores_eyebrow_installed">instalado</string>
|
||||
<string name="stores_eyebrow_library">en tu biblioteca</string>
|
||||
<string name="stores_eyebrow_store">tienda</string>
|
||||
@@ -2471,15 +2458,6 @@
|
||||
<string name="glasses_vr_fov">Campo de visión de SteamVR</string>
|
||||
<string name="glasses_vr_fov_hint">Más bajo acerca SteamVR y llena las gafas; más alto muestra más a tu alrededor.</string>
|
||||
<string name="glasses_playing">Jugando en %1$s</string>
|
||||
<string name="glasses_phone_recenter">Recentrar</string>
|
||||
<string name="glasses_phone_steamvr_menu">Menú de SteamVR</string>
|
||||
<string name="glasses_phone_switch_sticks">Sticks: manos ⇄ juego</string>
|
||||
<string name="glasses_phone_controllers">Controles: %1$s</string>
|
||||
<string name="glasses_phone_keyboard">Teclado</string>
|
||||
<string name="glasses_phone_session_menu">Menú de sesión</string>
|
||||
<string name="glasses_phone_stop_steamvr">Detener SteamVR</string>
|
||||
<string name="glasses_phone_brightness">Brillo %1$d / %2$d</string>
|
||||
<string name="glasses_phone_battery">Batería %1$d%% · %2$d °C</string>
|
||||
<string name="glasses_calibration">Calibración del seguimiento</string>
|
||||
<string name="glasses_calibration_hint">Reduce la deriva y alinea la referencia horizontal.</string>
|
||||
<string name="glasses_calibration_saved_hint">Estas gafas usan la calibración guardada.</string>
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
<string name="gesture_click_once">Еднократно щракване</string>
|
||||
<string name="gesture_hold_delay">Закъснение за задържане</string>
|
||||
<string name="gesture_double_delay">Интервал за двойно докосване</string>
|
||||
<string name="gesture_scope_game">Прилага се към текущата игра</string>
|
||||
<string name="gesture_scope_default">Настройки по подразбиране за всички игри</string>
|
||||
<string name="gesture_reset">Възстановяване на първоначалните настройки на жестовете</string>
|
||||
<string name="gesture_cancel">Отказ</string>
|
||||
<string name="gesture_action">Избор на действие</string>
|
||||
@@ -35,16 +37,23 @@
|
||||
<string name="gesture_scroll">Колелце на мишката</string>
|
||||
<string name="gesture_threshold">Праг на жестовете: %1$d пиксела</string>
|
||||
<string name="gesture_debug">Показване на разпознатия жест</string>
|
||||
<string name="gesture_editor_subtitle">Управление с мишка и клавиатура, подредено по жестове</string>
|
||||
<string name="gesture_tab_one">Един пръст</string>
|
||||
<string name="gesture_tab_two">Два пръста</string>
|
||||
<string name="gesture_tab_three">Три пръста</string>
|
||||
<string name="gesture_tab_options">Опции</string>
|
||||
<string name="gesture_one_hint">Изберете действието при докосване, плъзгане или задържане.</string>
|
||||
<string name="gesture_two_hint">Настройте преместването на камерата, мащабирането и вторичните щраквания.</string>
|
||||
<string name="gesture_three_hint">Дръжте допълнителните команди подръка.</string>
|
||||
<string name="gesture_options_hint">Настройте движението, разпознаването и визуалната обратна връзка.</string>
|
||||
<string name="gesture_defaults">Стандартни настройки</string>
|
||||
<string name="gesture_game">Тази игра</string>
|
||||
<string name="gesture_use_defaults">Използване на подразбиращите се</string>
|
||||
<string name="gesture_inheriting">Използват се настройките по подразбиране. Редактирането създава настройки за тази игра.</string>
|
||||
<string name="gesture_save_changes">Запазване на промените</string>
|
||||
<string name="gesture_close">Затвори</string>
|
||||
<string name="gesture_action_label">ДЕЙСТВИЕ</string>
|
||||
<string name="gesture_action_for">За %1$s</string>
|
||||
<string name="gesture_search_actions">Търсене на действия с мишката или клавиши</string>
|
||||
<string name="gesture_clear_search">Изчистване на търсенето</string>
|
||||
<string name="gesture_all_actions">Всички</string>
|
||||
@@ -59,9 +68,12 @@
|
||||
<string name="gesture_group_numpad">Цифрова клавиатура</string>
|
||||
<string name="gesture_group_symbols">Символи</string>
|
||||
<string name="gesture_movement">Движение на мишката</string>
|
||||
<string name="gesture_movement_hint">Директното плъзгане следва мястото на докосване. Относителното следва движението на пръста и е полезно, когато играта центрира мишката.</string>
|
||||
<string name="gesture_middle_relative_hint">Преместването със средния бутон винаги следва движението на пръста, за да няма скокове на курсора.</string>
|
||||
<string name="gesture_direct">Директно</string>
|
||||
<string name="gesture_relative">Относително</string>
|
||||
<string name="gesture_recognition">Разпознаване на жестове</string>
|
||||
<string name="gesture_threshold_hint">Изминете това разстояние, преди жест с два или три пръста да стане преместване или мащабиране.</string>
|
||||
<string name="gesture_feedback">Визуална обратна връзка</string>
|
||||
<string name="gesture_mouse_behavior">Поведение на бутона на мишката</string>
|
||||
<string name="gesture_ms">%1$d мс</string>
|
||||
|
||||
@@ -582,16 +582,6 @@
|
||||
<string name="drawer_prev_page">Предишна страница</string>
|
||||
<string name="drawer_next_page">Следваща страница</string>
|
||||
<string name="drawer_hud">HUD за производителност</string>
|
||||
<string name="drawer_hud_fps">Честота на кадрите</string>
|
||||
<string name="drawer_hud_battery">Температура на батерията</string>
|
||||
<string name="drawer_hud_graph">Графика на производителността</string>
|
||||
<string name="drawer_hud_color">Цвят</string>
|
||||
<string name="drawer_hud_color_mint">Мента</string>
|
||||
<string name="drawer_hud_color_white">Бяло</string>
|
||||
<string name="drawer_hud_color_sky">Небесно</string>
|
||||
<string name="drawer_hud_color_amber">Кехлибарено</string>
|
||||
<string name="drawer_hud_color_violet">Виолетово</string>
|
||||
<string name="drawer_hud_opacity">Прозрачност</string>
|
||||
<string name="drawer_scaling">Режим на мащабиране</string>
|
||||
<string name="drawer_scaling_sharpness">Острота на мащабиране</string>
|
||||
<string name="drawer_effects">Ефекти на екрана</string>
|
||||
@@ -2036,9 +2026,6 @@
|
||||
<string name="cloud_last_sync">Последна синхронизация %1$s</string>
|
||||
<string name="cloud_never">Още не е синхронизирано</string>
|
||||
<string name="cloud_done">%1$d файла</string>
|
||||
<string name="cloud_up_to_date">Вече е актуално</string>
|
||||
<string name="cloud_signed_out">Влезте в магазина, за да синхронизирате записите</string>
|
||||
<string name="cloud_not_synced">Записите не бяха синхронизирани</string>
|
||||
<string name="stores_eyebrow_installed">инсталирана</string>
|
||||
<string name="stores_eyebrow_library">в библиотеката ти</string>
|
||||
<string name="stores_eyebrow_store">магазин</string>
|
||||
@@ -2342,7 +2329,7 @@
|
||||
<string name="touch_done">Готово</string>
|
||||
<string name="touch_save_failed">Сензорното разположение не можа да се запази</string>
|
||||
<string name="touch_saved">Сензорното разположение е запазено в Steam</string>
|
||||
<string name="touch_app_id">Приложение %1$s</string>
|
||||
<string name="touch_app_id">Приложение %1$d</string>
|
||||
<string name="touch_label_hint">Етикет (незадължително)</string>
|
||||
<string name="touch_icons_missing">Иконите на Steam не бяха намерени</string>
|
||||
<plurals name="touch_icons_found">
|
||||
@@ -2433,15 +2420,6 @@
|
||||
<string name="glasses_vr_fov">Зрително поле на SteamVR</string>
|
||||
<string name="glasses_vr_fov_hint">По-ниско приближава SteamVR и запълва очилата; по-високо показва повече около вас.</string>
|
||||
<string name="glasses_playing">Играете на %1$s</string>
|
||||
<string name="glasses_phone_recenter">Центриране отново</string>
|
||||
<string name="glasses_phone_steamvr_menu">Меню на SteamVR</string>
|
||||
<string name="glasses_phone_switch_sticks">Стикове: ръце ⇄ игра</string>
|
||||
<string name="glasses_phone_controllers">Контролери: %1$s</string>
|
||||
<string name="glasses_phone_keyboard">Клавиатура</string>
|
||||
<string name="glasses_phone_session_menu">Меню на сесията</string>
|
||||
<string name="glasses_phone_stop_steamvr">Спиране на SteamVR</string>
|
||||
<string name="glasses_phone_brightness">Яркост %1$d / %2$d</string>
|
||||
<string name="glasses_phone_battery">Батерия %1$d%% · %2$d °C</string>
|
||||
<string name="glasses_calibration">Калибриране на проследяването</string>
|
||||
<string name="glasses_calibration_hint">Намалява дрейфа и подравнява хоризонта.</string>
|
||||
<string name="glasses_calibration_saved_hint">За тези очила се прилага запазено калибриране.</string>
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
<string name="gesture_click_once">Jednou kliknout</string>
|
||||
<string name="gesture_hold_delay">Prodleva podržení</string>
|
||||
<string name="gesture_double_delay">Interval dvojitého klepnutí</string>
|
||||
<string name="gesture_scope_game">Platí pro aktuální hru</string>
|
||||
<string name="gesture_scope_default">Výchozí nastavení pro všechny hry</string>
|
||||
<string name="gesture_reset">Obnovit původní nastavení gest</string>
|
||||
<string name="gesture_cancel">Zrušit</string>
|
||||
<string name="gesture_action">Vybrat akci</string>
|
||||
@@ -35,16 +37,23 @@
|
||||
<string name="gesture_scroll">Kolečko myši</string>
|
||||
<string name="gesture_threshold">Práh gest: %1$d px</string>
|
||||
<string name="gesture_debug">Zobrazovat rozpoznané gesto</string>
|
||||
<string name="gesture_editor_subtitle">Ovládání myši a klávesnice uspořádané podle gest</string>
|
||||
<string name="gesture_tab_one">Jeden prst</string>
|
||||
<string name="gesture_tab_two">Dva prsty</string>
|
||||
<string name="gesture_tab_three">Tři prsty</string>
|
||||
<string name="gesture_tab_options">Možnosti</string>
|
||||
<string name="gesture_one_hint">Vyberte akci pro klepnutí, tažení nebo podržení.</string>
|
||||
<string name="gesture_two_hint">Nastavte posouvání kamery, přiblížení a vedlejší kliknutí.</string>
|
||||
<string name="gesture_three_hint">Mějte další zkratky na dosah.</string>
|
||||
<string name="gesture_options_hint">Upravte pohyb, rozpoznávání a vizuální odezvu.</string>
|
||||
<string name="gesture_defaults">Výchozí</string>
|
||||
<string name="gesture_game">Tato hra</string>
|
||||
<string name="gesture_use_defaults">Použít výchozí</string>
|
||||
<string name="gesture_inheriting">Používá se výchozí nastavení. Úpravou vznikne nastavení pro tuto hru.</string>
|
||||
<string name="gesture_save_changes">Uložit změny</string>
|
||||
<string name="gesture_close">Zavřít</string>
|
||||
<string name="gesture_action_label">AKCE</string>
|
||||
<string name="gesture_action_for">Pro %1$s</string>
|
||||
<string name="gesture_search_actions">Hledat akce myši nebo klávesy</string>
|
||||
<string name="gesture_clear_search">Vymazat hledání</string>
|
||||
<string name="gesture_all_actions">Vše</string>
|
||||
@@ -59,9 +68,12 @@
|
||||
<string name="gesture_group_numpad">Numerická klávesnice</string>
|
||||
<string name="gesture_group_symbols">Symboly</string>
|
||||
<string name="gesture_movement">Pohyb myši</string>
|
||||
<string name="gesture_movement_hint">Přímé tažení sleduje polohu dotyku. Relativní tažení sleduje pohyb prstu a hodí se, když hra vrací myš do středu.</string>
|
||||
<string name="gesture_middle_relative_hint">Posouvání prostředním tlačítkem vždy sleduje pohyb prstu, aby kurzor nepřeskakoval.</string>
|
||||
<string name="gesture_direct">Přímé</string>
|
||||
<string name="gesture_relative">Relativní</string>
|
||||
<string name="gesture_recognition">Rozpoznávání gest</string>
|
||||
<string name="gesture_threshold_hint">Po překonání této vzdálenosti se gesto dvěma či třemi prsty změní na posouvání nebo přiblížení.</string>
|
||||
<string name="gesture_feedback">Vizuální odezva</string>
|
||||
<string name="gesture_mouse_behavior">Chování tlačítka myši</string>
|
||||
<string name="gesture_ms">%1$d ms</string>
|
||||
|
||||
@@ -586,16 +586,6 @@
|
||||
<string name="drawer_prev_page">Předchozí stránka</string>
|
||||
<string name="drawer_next_page">Další stránka</string>
|
||||
<string name="drawer_hud">HUD výkonu</string>
|
||||
<string name="drawer_hud_fps">Snímková frekvence</string>
|
||||
<string name="drawer_hud_battery">Teplota baterie</string>
|
||||
<string name="drawer_hud_graph">Graf výkonu</string>
|
||||
<string name="drawer_hud_color">Barva</string>
|
||||
<string name="drawer_hud_color_mint">Máta</string>
|
||||
<string name="drawer_hud_color_white">Bílá</string>
|
||||
<string name="drawer_hud_color_sky">Nebeská</string>
|
||||
<string name="drawer_hud_color_amber">Ambrová</string>
|
||||
<string name="drawer_hud_color_violet">Fialová</string>
|
||||
<string name="drawer_hud_opacity">Průhlednost</string>
|
||||
<string name="drawer_scaling">Režim škálování</string>
|
||||
<string name="drawer_scaling_sharpness">Ostrost škálování</string>
|
||||
<string name="drawer_effects">Efekty obrazovky</string>
|
||||
@@ -2078,9 +2068,6 @@
|
||||
<string name="cloud_last_sync">Poslední synchronizace %1$s</string>
|
||||
<string name="cloud_never">Ještě nesynchronizováno</string>
|
||||
<string name="cloud_done">%1$d souborů</string>
|
||||
<string name="cloud_up_to_date">Už je aktuální</string>
|
||||
<string name="cloud_signed_out">Pro synchronizaci uložených pozic se přihlaste do obchodu</string>
|
||||
<string name="cloud_not_synced">Uložené pozice nebyly synchronizovány</string>
|
||||
<string name="stores_eyebrow_installed">nainstalováno</string>
|
||||
<string name="stores_eyebrow_library">ve vaší knihovně</string>
|
||||
<string name="stores_eyebrow_store">obchod</string>
|
||||
@@ -2388,7 +2375,7 @@
|
||||
<string name="touch_done">Hotovo</string>
|
||||
<string name="touch_save_failed">Dotykové rozložení se nepodařilo uložit</string>
|
||||
<string name="touch_saved">Dotykové rozložení uloženo ve Steamu</string>
|
||||
<string name="touch_app_id">Aplikace %1$s</string>
|
||||
<string name="touch_app_id">Aplikace %1$d</string>
|
||||
<string name="touch_label_hint">Popisek (nepovinné)</string>
|
||||
<string name="touch_icons_missing">Ikony Steamu nebyly nalezeny</string>
|
||||
<plurals name="touch_icons_found">
|
||||
@@ -2483,15 +2470,6 @@
|
||||
<string name="glasses_vr_fov">Zorné pole SteamVR</string>
|
||||
<string name="glasses_vr_fov_hint">Nižší hodnota přiblíží SteamVR a vyplní brýle; vyšší ukáže víc z okolí.</string>
|
||||
<string name="glasses_playing">Hraje se na %1$s</string>
|
||||
<string name="glasses_phone_recenter">Znovu vycentrovat</string>
|
||||
<string name="glasses_phone_steamvr_menu">Nabídka SteamVR</string>
|
||||
<string name="glasses_phone_switch_sticks">Páčky: ruce ⇄ hra</string>
|
||||
<string name="glasses_phone_controllers">Ovladače: %1$s</string>
|
||||
<string name="glasses_phone_keyboard">Klávesnice</string>
|
||||
<string name="glasses_phone_session_menu">Nabídka relace</string>
|
||||
<string name="glasses_phone_stop_steamvr">Zastavit SteamVR</string>
|
||||
<string name="glasses_phone_brightness">Jas %1$d / %2$d</string>
|
||||
<string name="glasses_phone_battery">Baterie %1$d%% · %2$d °C</string>
|
||||
<string name="glasses_calibration">Kalibrace sledování</string>
|
||||
<string name="glasses_calibration_hint">Sníží drift a vyrovná vodorovnou referenci.</string>
|
||||
<string name="glasses_calibration_saved_hint">Pro tyto brýle se používá uložená kalibrace.</string>
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
<string name="gesture_click_once">Klik én gang</string>
|
||||
<string name="gesture_hold_delay">Forsinkelse før hold</string>
|
||||
<string name="gesture_double_delay">Interval for dobbelttryk</string>
|
||||
<string name="gesture_scope_game">Gælder det aktuelle spil</string>
|
||||
<string name="gesture_scope_default">Standardindstillinger for alle spil</string>
|
||||
<string name="gesture_reset">Gendan de oprindelige bevægelsesindstillinger</string>
|
||||
<string name="gesture_cancel">Annuller</string>
|
||||
<string name="gesture_action">Vælg handling</string>
|
||||
@@ -35,16 +37,23 @@
|
||||
<string name="gesture_scroll">Musehjul</string>
|
||||
<string name="gesture_threshold">Bevægelsestærskel: %1$d px</string>
|
||||
<string name="gesture_debug">Vis genkendt bevægelse</string>
|
||||
<string name="gesture_editor_subtitle">Muse- og tastaturstyring organiseret efter bevægelse</string>
|
||||
<string name="gesture_tab_one">Én finger</string>
|
||||
<string name="gesture_tab_two">To fingre</string>
|
||||
<string name="gesture_tab_three">Tre fingre</string>
|
||||
<string name="gesture_tab_options">Indstillinger</string>
|
||||
<string name="gesture_one_hint">Vælg, hvad et tryk, træk eller hold skal gøre.</string>
|
||||
<string name="gesture_two_hint">Indstil kamerapanorering, zoom og sekundære klik.</string>
|
||||
<string name="gesture_three_hint">Hav flere genveje inden for rækkevidde.</string>
|
||||
<string name="gesture_options_hint">Juster bevægelse, genkendelse og visuel feedback.</string>
|
||||
<string name="gesture_defaults">Standarder</string>
|
||||
<string name="gesture_game">Dette spil</string>
|
||||
<string name="gesture_use_defaults">Brug standardindstillinger</string>
|
||||
<string name="gesture_inheriting">Bruger standardindstillingerne. Redigering opretter særlige indstillinger for spillet.</string>
|
||||
<string name="gesture_save_changes">Gem ændringer</string>
|
||||
<string name="gesture_close">Luk</string>
|
||||
<string name="gesture_action_label">HANDLING</string>
|
||||
<string name="gesture_action_for">For %1$s</string>
|
||||
<string name="gesture_search_actions">Søg efter musehandlinger eller tastaturtaster</string>
|
||||
<string name="gesture_clear_search">Ryd søgning</string>
|
||||
<string name="gesture_all_actions">Alle</string>
|
||||
@@ -59,9 +68,12 @@
|
||||
<string name="gesture_group_numpad">Numerisk tastatur</string>
|
||||
<string name="gesture_group_symbols">Symboler</string>
|
||||
<string name="gesture_movement">Musebevægelse</string>
|
||||
<string name="gesture_movement_hint">Direkte træk følger berøringspositionen. Relative træk følger fingerbevægelsen og er nyttige, når et spil centrerer musen.</string>
|
||||
<string name="gesture_middle_relative_hint">Panorering med midterste museknap følger altid fingerbevægelsen for at undgå markørspring.</string>
|
||||
<string name="gesture_direct">Direkte</string>
|
||||
<string name="gesture_relative">Relativ</string>
|
||||
<string name="gesture_recognition">Genkendelse af bevægelser</string>
|
||||
<string name="gesture_threshold_hint">Bevæg fingrene så langt, før en bevægelse med to eller tre fingre bliver til panorering eller zoom.</string>
|
||||
<string name="gesture_feedback">Visuel feedback</string>
|
||||
<string name="gesture_mouse_behavior">Museknappens funktion</string>
|
||||
<string name="gesture_ms">%1$d ms</string>
|
||||
|
||||
@@ -582,16 +582,6 @@
|
||||
<string name="drawer_prev_page">Forrige side</string>
|
||||
<string name="drawer_next_page">Næste side</string>
|
||||
<string name="drawer_hud">Ydelses-HUD</string>
|
||||
<string name="drawer_hud_fps">Billedhastighed</string>
|
||||
<string name="drawer_hud_battery">Batteritemperatur</string>
|
||||
<string name="drawer_hud_graph">Ydelsesgraf</string>
|
||||
<string name="drawer_hud_color">Farve</string>
|
||||
<string name="drawer_hud_color_mint">Mint</string>
|
||||
<string name="drawer_hud_color_white">Hvid</string>
|
||||
<string name="drawer_hud_color_sky">Himmelblå</string>
|
||||
<string name="drawer_hud_color_amber">Ravgul</string>
|
||||
<string name="drawer_hud_color_violet">Violet</string>
|
||||
<string name="drawer_hud_opacity">Gennemsigtighed</string>
|
||||
<string name="drawer_scaling">Skaleringstilstand</string>
|
||||
<string name="drawer_scaling_sharpness">Skaleringsskarphed</string>
|
||||
<string name="drawer_effects">Skærmeffekter</string>
|
||||
@@ -2036,9 +2026,6 @@
|
||||
<string name="cloud_last_sync">Senest synkroniseret %1$s</string>
|
||||
<string name="cloud_never">Ikke synkroniseret endnu</string>
|
||||
<string name="cloud_done">%1$d filer</string>
|
||||
<string name="cloud_up_to_date">Allerede opdateret</string>
|
||||
<string name="cloud_signed_out">Log ind i butikken for at synkronisere gemte spil</string>
|
||||
<string name="cloud_not_synced">Gemte spil blev ikke synkroniseret</string>
|
||||
<string name="stores_eyebrow_installed">installeret</string>
|
||||
<string name="stores_eyebrow_library">i dit bibliotek</string>
|
||||
<string name="stores_eyebrow_store">butik</string>
|
||||
@@ -2342,7 +2329,7 @@
|
||||
<string name="touch_done">Udført</string>
|
||||
<string name="touch_save_failed">Berøringslayoutet kunne ikke gemmes</string>
|
||||
<string name="touch_saved">Berøringslayout gemt i Steam</string>
|
||||
<string name="touch_app_id">App %1$s</string>
|
||||
<string name="touch_app_id">App %1$d</string>
|
||||
<string name="touch_label_hint">Etiket (valgfrit)</string>
|
||||
<string name="touch_icons_missing">Steams ikoner blev ikke fundet</string>
|
||||
<plurals name="touch_icons_found">
|
||||
@@ -2433,15 +2420,6 @@
|
||||
<string name="glasses_vr_fov">SteamVR-synsfelt</string>
|
||||
<string name="glasses_vr_fov_hint">Lavere bringer SteamVR tættere på og fylder brillen; højere viser mere omkring dig.</string>
|
||||
<string name="glasses_playing">Spiller på %1$s</string>
|
||||
<string name="glasses_phone_recenter">Centrér igen</string>
|
||||
<string name="glasses_phone_steamvr_menu">SteamVR-menu</string>
|
||||
<string name="glasses_phone_switch_sticks">Pinde: hænder ⇄ spil</string>
|
||||
<string name="glasses_phone_controllers">Controllere: %1$s</string>
|
||||
<string name="glasses_phone_keyboard">Tastatur</string>
|
||||
<string name="glasses_phone_session_menu">Sessionsmenu</string>
|
||||
<string name="glasses_phone_stop_steamvr">Stop SteamVR</string>
|
||||
<string name="glasses_phone_brightness">Lysstyrke %1$d / %2$d</string>
|
||||
<string name="glasses_phone_battery">Batteri %1$d%% · %2$d °C</string>
|
||||
<string name="glasses_calibration">Sporingskalibrering</string>
|
||||
<string name="glasses_calibration_hint">Mindsker drift og justerer den vandrette reference.</string>
|
||||
<string name="glasses_calibration_saved_hint">Gemt kalibrering anvendes på denne brille.</string>
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
<string name="gesture_click_once">Einmal klicken</string>
|
||||
<string name="gesture_hold_delay">Halteverzögerung</string>
|
||||
<string name="gesture_double_delay">Doppeltipp-Intervall</string>
|
||||
<string name="gesture_scope_game">Gilt für das aktuelle Spiel</string>
|
||||
<string name="gesture_scope_default">Standardeinstellungen für alle Spiele</string>
|
||||
<string name="gesture_reset">Ursprüngliche Gesteneinstellungen wiederherstellen</string>
|
||||
<string name="gesture_cancel">Abbrechen</string>
|
||||
<string name="gesture_action">Aktion auswählen</string>
|
||||
@@ -35,16 +37,23 @@
|
||||
<string name="gesture_scroll">Mausrad</string>
|
||||
<string name="gesture_threshold">Gestenschwelle: %1$d px</string>
|
||||
<string name="gesture_debug">Erkannte Geste anzeigen</string>
|
||||
<string name="gesture_editor_subtitle">Maus- und Tastatursteuerung nach Gesten geordnet</string>
|
||||
<string name="gesture_tab_one">Ein Finger</string>
|
||||
<string name="gesture_tab_two">Zwei Finger</string>
|
||||
<string name="gesture_tab_three">Drei Finger</string>
|
||||
<string name="gesture_tab_options">Optionen</string>
|
||||
<string name="gesture_one_hint">Wähle die Aktion für Tippen, Ziehen oder Halten.</string>
|
||||
<string name="gesture_two_hint">Kameraschwenks, Zoom und sekundäre Klicks anpassen.</string>
|
||||
<string name="gesture_three_hint">Zusätzliche Kurzbefehle griffbereit halten.</string>
|
||||
<string name="gesture_options_hint">Bewegung, Erkennung und visuelle Rückmeldung anpassen.</string>
|
||||
<string name="gesture_defaults">Standardwerte</string>
|
||||
<string name="gesture_game">Dieses Spiel</string>
|
||||
<string name="gesture_use_defaults">Standardeinstellungen verwenden</string>
|
||||
<string name="gesture_inheriting">Standardeinstellungen werden verwendet. Änderungen erstellen eine Spielanpassung.</string>
|
||||
<string name="gesture_save_changes">Änderungen speichern</string>
|
||||
<string name="gesture_close">Schließen</string>
|
||||
<string name="gesture_action_label">AKTION</string>
|
||||
<string name="gesture_action_for">Für %1$s</string>
|
||||
<string name="gesture_search_actions">Mausaktionen oder Tastaturtasten suchen</string>
|
||||
<string name="gesture_clear_search">Suche leeren</string>
|
||||
<string name="gesture_all_actions">Alle</string>
|
||||
@@ -59,9 +68,12 @@
|
||||
<string name="gesture_group_numpad">Ziffernblock</string>
|
||||
<string name="gesture_group_symbols">Symbole</string>
|
||||
<string name="gesture_movement">Mausbewegung</string>
|
||||
<string name="gesture_movement_hint">Direktes Ziehen folgt der Berührungsposition. Relatives Ziehen folgt der Fingerbewegung und eignet sich für Spiele, die die Maus zentrieren.</string>
|
||||
<string name="gesture_middle_relative_hint">Schwenks mit mittlerer Maustaste folgen immer der Fingerbewegung, um Zeigersprünge zu vermeiden.</string>
|
||||
<string name="gesture_direct">Direkt</string>
|
||||
<string name="gesture_relative">Relativ</string>
|
||||
<string name="gesture_recognition">Gestenerkennung</string>
|
||||
<string name="gesture_threshold_hint">Bewege die Finger um diese Strecke, bevor eine Zwei- oder Dreifingergeste als Schwenken oder Zoomen gilt.</string>
|
||||
<string name="gesture_feedback">Visuelle Rückmeldung</string>
|
||||
<string name="gesture_mouse_behavior">Verhalten der Maustaste</string>
|
||||
<string name="gesture_ms">%1$d ms</string>
|
||||
|
||||
@@ -579,16 +579,6 @@
|
||||
<string name="drawer_prev_page">Vorherige Seite</string>
|
||||
<string name="drawer_next_page">Nächste Seite</string>
|
||||
<string name="drawer_hud">Leistungs-HUD</string>
|
||||
<string name="drawer_hud_fps">Bildrate</string>
|
||||
<string name="drawer_hud_battery">Akkutemperatur</string>
|
||||
<string name="drawer_hud_graph">Leistungsdiagramm</string>
|
||||
<string name="drawer_hud_color">Farbe</string>
|
||||
<string name="drawer_hud_color_mint">Minze</string>
|
||||
<string name="drawer_hud_color_white">Weiß</string>
|
||||
<string name="drawer_hud_color_sky">Himmelblau</string>
|
||||
<string name="drawer_hud_color_amber">Bernstein</string>
|
||||
<string name="drawer_hud_color_violet">Violett</string>
|
||||
<string name="drawer_hud_opacity">Deckkraft</string>
|
||||
<string name="drawer_scaling">Skalierungsmodus</string>
|
||||
<string name="drawer_scaling_sharpness">Skalierungsschärfe</string>
|
||||
<string name="drawer_effects">Bildschirmeffekte</string>
|
||||
@@ -2025,9 +2015,6 @@
|
||||
<string name="cloud_last_sync">Zuletzt synchronisiert %1$s</string>
|
||||
<string name="cloud_never">Noch nicht synchronisiert</string>
|
||||
<string name="cloud_done">%1$d Dateien</string>
|
||||
<string name="cloud_up_to_date">Bereits aktuell</string>
|
||||
<string name="cloud_signed_out">Im Store anmelden, um Spielstände zu synchronisieren</string>
|
||||
<string name="cloud_not_synced">Spielstände wurden nicht synchronisiert</string>
|
||||
<string name="stores_eyebrow_installed">installiert</string>
|
||||
<string name="stores_eyebrow_library">in deiner Bibliothek</string>
|
||||
<string name="stores_eyebrow_store">Store</string>
|
||||
@@ -2328,7 +2315,7 @@
|
||||
<string name="touch_done">Fertig</string>
|
||||
<string name="touch_save_failed">Das Touch-Layout konnte nicht gespeichert werden</string>
|
||||
<string name="touch_saved">Touch-Layout in Steam gespeichert</string>
|
||||
<string name="touch_app_id">App %1$s</string>
|
||||
<string name="touch_app_id">App %1$d</string>
|
||||
<string name="touch_label_hint">Bezeichnung (optional)</string>
|
||||
<string name="touch_icons_missing">Steams Symbole wurden nicht gefunden</string>
|
||||
<plurals name="touch_icons_found">
|
||||
@@ -2419,15 +2406,6 @@
|
||||
<string name="glasses_vr_fov">SteamVR-Sichtfeld</string>
|
||||
<string name="glasses_vr_fov_hint">Niedriger holt SteamVR näher heran und füllt die Brille; höher zeigt mehr von deiner Umgebung.</string>
|
||||
<string name="glasses_playing">Spiel läuft auf %1$s</string>
|
||||
<string name="glasses_phone_recenter">Neu zentrieren</string>
|
||||
<string name="glasses_phone_steamvr_menu">SteamVR-Menü</string>
|
||||
<string name="glasses_phone_switch_sticks">Sticks: Hände ⇄ Spiel</string>
|
||||
<string name="glasses_phone_controllers">Controller: %1$s</string>
|
||||
<string name="glasses_phone_keyboard">Tastatur</string>
|
||||
<string name="glasses_phone_session_menu">Sitzungsmenü</string>
|
||||
<string name="glasses_phone_stop_steamvr">SteamVR beenden</string>
|
||||
<string name="glasses_phone_brightness">Helligkeit %1$d / %2$d</string>
|
||||
<string name="glasses_phone_battery">Akku %1$d%% · %2$d °C</string>
|
||||
<string name="glasses_calibration">Tracking-Kalibrierung</string>
|
||||
<string name="glasses_calibration_hint">Verringert Drift und richtet die waagerechte Referenz aus.</string>
|
||||
<string name="glasses_calibration_saved_hint">Für diese Brille wird eine gespeicherte Kalibrierung verwendet.</string>
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
<string name="gesture_click_once">Ένα κλικ</string>
|
||||
<string name="gesture_hold_delay">Καθυστέρηση κρατήματος</string>
|
||||
<string name="gesture_double_delay">Διάστημα διπλού πατήματος</string>
|
||||
<string name="gesture_scope_game">Ισχύει για το τρέχον παιχνίδι</string>
|
||||
<string name="gesture_scope_default">Προεπιλεγμένες ρυθμίσεις για όλα τα παιχνίδια</string>
|
||||
<string name="gesture_reset">Επαναφορά αρχικών ρυθμίσεων χειρονομιών</string>
|
||||
<string name="gesture_cancel">Ακύρωση</string>
|
||||
<string name="gesture_action">Επιλογή ενέργειας</string>
|
||||
@@ -35,16 +37,23 @@
|
||||
<string name="gesture_scroll">Ροδέλα ποντικιού</string>
|
||||
<string name="gesture_threshold">Όριο χειρονομιών: %1$d px</string>
|
||||
<string name="gesture_debug">Εμφάνιση αναγνωρισμένης χειρονομίας</string>
|
||||
<string name="gesture_editor_subtitle">Χειρισμοί ποντικιού και πληκτρολογίου ανά χειρονομία</string>
|
||||
<string name="gesture_tab_one">Ένα δάχτυλο</string>
|
||||
<string name="gesture_tab_two">Δύο δάχτυλα</string>
|
||||
<string name="gesture_tab_three">Τρία δάχτυλα</string>
|
||||
<string name="gesture_tab_options">Επιλογές</string>
|
||||
<string name="gesture_one_hint">Επιλέξτε την ενέργεια για πάτημα, σύρσιμο ή κράτημα.</string>
|
||||
<string name="gesture_two_hint">Ρυθμίστε τη μετατόπιση της κάμερας, τη μεγέθυνση και τα δευτερεύοντα κλικ.</string>
|
||||
<string name="gesture_three_hint">Κρατήστε επιπλέον συντομεύσεις κοντά σας.</string>
|
||||
<string name="gesture_options_hint">Ρυθμίστε την κίνηση, την αναγνώριση και την οπτική ανάδραση.</string>
|
||||
<string name="gesture_defaults">Προεπιλογές</string>
|
||||
<string name="gesture_game">Αυτό το παιχνίδι</string>
|
||||
<string name="gesture_use_defaults">Χρήση προεπιλογών</string>
|
||||
<string name="gesture_inheriting">Χρησιμοποιούνται οι προεπιλογές. Η επεξεργασία δημιουργεί ρυθμίσεις για αυτό το παιχνίδι.</string>
|
||||
<string name="gesture_save_changes">Αποθήκευση αλλαγών</string>
|
||||
<string name="gesture_close">Κλείσιμο</string>
|
||||
<string name="gesture_action_label">ΕΝΕΡΓΕΙΑ</string>
|
||||
<string name="gesture_action_for">Για %1$s</string>
|
||||
<string name="gesture_search_actions">Αναζήτηση ενεργειών ποντικιού ή πλήκτρων</string>
|
||||
<string name="gesture_clear_search">Εκκαθάριση αναζήτησης</string>
|
||||
<string name="gesture_all_actions">Όλα</string>
|
||||
@@ -59,9 +68,12 @@
|
||||
<string name="gesture_group_numpad">Αριθμητικό πληκτρολόγιο</string>
|
||||
<string name="gesture_group_symbols">Σύμβολα</string>
|
||||
<string name="gesture_movement">Κίνηση ποντικιού</string>
|
||||
<string name="gesture_movement_hint">Το άμεσο σύρσιμο ακολουθεί τη θέση αφής. Το σχετικό σύρσιμο ακολουθεί την κίνηση του δαχτύλου, χρήσιμο όταν το παιχνίδι επαναφέρει το ποντίκι στο κέντρο.</string>
|
||||
<string name="gesture_middle_relative_hint">Η μετατόπιση με το μεσαίο κουμπί ακολουθεί πάντα την κίνηση του δαχτύλου για να αποφεύγονται άλματα του δείκτη.</string>
|
||||
<string name="gesture_direct">Απευθείας</string>
|
||||
<string name="gesture_relative">Σχετική</string>
|
||||
<string name="gesture_recognition">Αναγνώριση χειρονομιών</string>
|
||||
<string name="gesture_threshold_hint">Κινηθείτε κατά αυτή την απόσταση προτού μια χειρονομία με δύο ή τρία δάχτυλα γίνει μετατόπιση ή μεγέθυνση.</string>
|
||||
<string name="gesture_feedback">Οπτική ανάδραση</string>
|
||||
<string name="gesture_mouse_behavior">Συμπεριφορά κουμπιού ποντικιού</string>
|
||||
<string name="gesture_ms">%1$d ms</string>
|
||||
|
||||
@@ -582,16 +582,6 @@
|
||||
<string name="drawer_prev_page">Προηγούμενη σελίδα</string>
|
||||
<string name="drawer_next_page">Επόμενη σελίδα</string>
|
||||
<string name="drawer_hud">HUD απόδοσης</string>
|
||||
<string name="drawer_hud_fps">Ρυθμός καρέ</string>
|
||||
<string name="drawer_hud_battery">Θερμοκρασία μπαταρίας</string>
|
||||
<string name="drawer_hud_graph">Γράφημα απόδοσης</string>
|
||||
<string name="drawer_hud_color">Χρώμα</string>
|
||||
<string name="drawer_hud_color_mint">Μέντα</string>
|
||||
<string name="drawer_hud_color_white">Λευκό</string>
|
||||
<string name="drawer_hud_color_sky">Γαλάζιο</string>
|
||||
<string name="drawer_hud_color_amber">Κεχριμπαρί</string>
|
||||
<string name="drawer_hud_color_violet">Βιολετί</string>
|
||||
<string name="drawer_hud_opacity">Διαφάνεια</string>
|
||||
<string name="drawer_scaling">Λειτουργία κλιμάκωσης</string>
|
||||
<string name="drawer_scaling_sharpness">Οξύτητα κλιμάκωσης</string>
|
||||
<string name="drawer_effects">Εφέ οθόνης</string>
|
||||
@@ -2036,9 +2026,6 @@
|
||||
<string name="cloud_last_sync">Τελευταίος συγχρονισμός %1$s</string>
|
||||
<string name="cloud_never">Δεν έχει συγχρονιστεί ακόμα</string>
|
||||
<string name="cloud_done">%1$d αρχεία</string>
|
||||
<string name="cloud_up_to_date">Ήδη ενημερωμένο</string>
|
||||
<string name="cloud_signed_out">Συνδεθείτε στο κατάστημα για συγχρονισμό αποθηκεύσεων</string>
|
||||
<string name="cloud_not_synced">Οι αποθηκεύσεις δεν συγχρονίστηκαν</string>
|
||||
<string name="stores_eyebrow_installed">εγκατεστημένο</string>
|
||||
<string name="stores_eyebrow_library">στη βιβλιοθήκη σας</string>
|
||||
<string name="stores_eyebrow_store">κατάστημα</string>
|
||||
@@ -2342,7 +2329,7 @@
|
||||
<string name="touch_done">Τέλος</string>
|
||||
<string name="touch_save_failed">Δεν ήταν δυνατή η αποθήκευση της διάταξης αφής</string>
|
||||
<string name="touch_saved">Η διάταξη αφής αποθηκεύτηκε στο Steam</string>
|
||||
<string name="touch_app_id">Εφαρμογή %1$s</string>
|
||||
<string name="touch_app_id">Εφαρμογή %1$d</string>
|
||||
<string name="touch_label_hint">Ετικέτα (προαιρετικό)</string>
|
||||
<string name="touch_icons_missing">Τα εικονίδια του Steam δεν βρέθηκαν</string>
|
||||
<plurals name="touch_icons_found">
|
||||
@@ -2433,15 +2420,6 @@
|
||||
<string name="glasses_vr_fov">Οπτικό πεδίο SteamVR</string>
|
||||
<string name="glasses_vr_fov_hint">Χαμηλότερο: το SteamVR έρχεται πιο κοντά και γεμίζει τα γυαλιά· υψηλότερο: βλέπετε περισσότερα γύρω σας.</string>
|
||||
<string name="glasses_playing">Παίζετε στα %1$s</string>
|
||||
<string name="glasses_phone_recenter">Επανακεντράρισμα</string>
|
||||
<string name="glasses_phone_steamvr_menu">Μενού SteamVR</string>
|
||||
<string name="glasses_phone_switch_sticks">Μοχλοί: χέρια ⇄ παιχνίδι</string>
|
||||
<string name="glasses_phone_controllers">Χειριστήρια: %1$s</string>
|
||||
<string name="glasses_phone_keyboard">Πληκτρολόγιο</string>
|
||||
<string name="glasses_phone_session_menu">Μενού συνεδρίας</string>
|
||||
<string name="glasses_phone_stop_steamvr">Διακοπή SteamVR</string>
|
||||
<string name="glasses_phone_brightness">Φωτεινότητα %1$d / %2$d</string>
|
||||
<string name="glasses_phone_battery">Μπαταρία %1$d%% · %2$d °C</string>
|
||||
<string name="glasses_calibration">Βαθμονόμηση παρακολούθησης</string>
|
||||
<string name="glasses_calibration_hint">Μειώνει την ολίσθηση και ευθυγραμμίζει την οριζόντια αναφορά.</string>
|
||||
<string name="glasses_calibration_saved_hint">Σε αυτά τα γυαλιά εφαρμόζεται αποθηκευμένη βαθμονόμηση.</string>
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
<string name="gesture_click_once">Hacer clic una vez</string>
|
||||
<string name="gesture_hold_delay">Tiempo de pulsación sostenida</string>
|
||||
<string name="gesture_double_delay">Intervalo de doble toque</string>
|
||||
<string name="gesture_scope_game">Se aplica al juego actual</string>
|
||||
<string name="gesture_scope_default">Ajustes predeterminados para todos los juegos</string>
|
||||
<string name="gesture_reset">Restablecer los ajustes originales de gestos</string>
|
||||
<string name="gesture_cancel">Cancelar</string>
|
||||
<string name="gesture_action">Elegir acción</string>
|
||||
@@ -35,16 +37,23 @@
|
||||
<string name="gesture_scroll">Rueda del ratón</string>
|
||||
<string name="gesture_threshold">Umbral de gestos: %1$d px</string>
|
||||
<string name="gesture_debug">Mostrar gesto reconocido</string>
|
||||
<string name="gesture_editor_subtitle">Controles de ratón y teclado organizados por gesto</string>
|
||||
<string name="gesture_tab_one">Un dedo</string>
|
||||
<string name="gesture_tab_two">Dos dedos</string>
|
||||
<string name="gesture_tab_three">Tres dedos</string>
|
||||
<string name="gesture_tab_options">Opciones</string>
|
||||
<string name="gesture_one_hint">Elige qué debe hacer un toque, arrastre o pulsación sostenida.</string>
|
||||
<string name="gesture_two_hint">Configura el movimiento de cámara, el zoom y los clics secundarios.</string>
|
||||
<string name="gesture_three_hint">Ten más accesos directos al alcance de la mano.</string>
|
||||
<string name="gesture_options_hint">Ajusta el movimiento, el reconocimiento y la respuesta visual.</string>
|
||||
<string name="gesture_defaults">Predeterminados</string>
|
||||
<string name="gesture_game">Este juego</string>
|
||||
<string name="gesture_use_defaults">Usar valores predeterminados</string>
|
||||
<string name="gesture_inheriting">Se usan los ajustes predeterminados. Al editarlos, se crean ajustes específicos del juego.</string>
|
||||
<string name="gesture_save_changes">Guardar cambios</string>
|
||||
<string name="gesture_close">Cerrar</string>
|
||||
<string name="gesture_action_label">ACCIÓN</string>
|
||||
<string name="gesture_action_for">Para %1$s</string>
|
||||
<string name="gesture_search_actions">Buscar acciones de ratón o teclas del teclado</string>
|
||||
<string name="gesture_clear_search">Borrar búsqueda</string>
|
||||
<string name="gesture_all_actions">Todas</string>
|
||||
@@ -59,9 +68,12 @@
|
||||
<string name="gesture_group_numpad">Teclado numérico</string>
|
||||
<string name="gesture_group_symbols">Símbolos</string>
|
||||
<string name="gesture_movement">Movimiento del ratón</string>
|
||||
<string name="gesture_movement_hint">El arrastre directo sigue la posición del toque. El relativo sigue el movimiento del dedo y resulta útil cuando un juego centra el ratón.</string>
|
||||
<string name="gesture_middle_relative_hint">El desplazamiento con el botón central siempre sigue el movimiento del dedo para evitar saltos del cursor.</string>
|
||||
<string name="gesture_direct">Directo</string>
|
||||
<string name="gesture_relative">Relativo</string>
|
||||
<string name="gesture_recognition">Reconocimiento de gestos</string>
|
||||
<string name="gesture_threshold_hint">Recorre esta distancia antes de que un gesto con dos o tres dedos se convierta en desplazamiento o zoom.</string>
|
||||
<string name="gesture_feedback">Respuesta visual</string>
|
||||
<string name="gesture_mouse_behavior">Comportamiento del botón del ratón</string>
|
||||
<string name="gesture_ms">%1$d ms</string>
|
||||
|
||||
@@ -581,16 +581,6 @@
|
||||
<string name="drawer_prev_page">Página anterior</string>
|
||||
<string name="drawer_next_page">Página siguiente</string>
|
||||
<string name="drawer_hud">HUD de rendimiento</string>
|
||||
<string name="drawer_hud_fps">Tasa de fotogramas</string>
|
||||
<string name="drawer_hud_battery">Temperatura de la batería</string>
|
||||
<string name="drawer_hud_graph">Gráfico de rendimiento</string>
|
||||
<string name="drawer_hud_color">Color</string>
|
||||
<string name="drawer_hud_color_mint">Menta</string>
|
||||
<string name="drawer_hud_color_white">Blanco</string>
|
||||
<string name="drawer_hud_color_sky">Celeste</string>
|
||||
<string name="drawer_hud_color_amber">Ámbar</string>
|
||||
<string name="drawer_hud_color_violet">Violeta</string>
|
||||
<string name="drawer_hud_opacity">Opacidad</string>
|
||||
<string name="drawer_scaling">Modo de escalado</string>
|
||||
<string name="drawer_scaling_sharpness">Nitidez del escalado</string>
|
||||
<string name="drawer_effects">Efectos de pantalla</string>
|
||||
@@ -2054,9 +2044,6 @@
|
||||
<string name="cloud_last_sync">Última sincronización %1$s</string>
|
||||
<string name="cloud_never">Aún sin sincronizar</string>
|
||||
<string name="cloud_done">%1$d archivos</string>
|
||||
<string name="cloud_up_to_date">Ya está al día</string>
|
||||
<string name="cloud_signed_out">Inicia sesión en la tienda para sincronizar las partidas</string>
|
||||
<string name="cloud_not_synced">No se sincronizaron las partidas</string>
|
||||
<string name="stores_eyebrow_installed">instalado</string>
|
||||
<string name="stores_eyebrow_library">en tu biblioteca</string>
|
||||
<string name="stores_eyebrow_store">tienda</string>
|
||||
@@ -2359,7 +2346,7 @@
|
||||
<string name="touch_done">Hecho</string>
|
||||
<string name="touch_save_failed">No se pudo guardar la disposición táctil</string>
|
||||
<string name="touch_saved">Disposición táctil guardada en Steam</string>
|
||||
<string name="touch_app_id">App %1$s</string>
|
||||
<string name="touch_app_id">App %1$d</string>
|
||||
<string name="touch_label_hint">Etiqueta (opcional)</string>
|
||||
<string name="touch_icons_missing">No se encontraron los iconos de Steam</string>
|
||||
<plurals name="touch_icons_found">
|
||||
@@ -2441,15 +2428,6 @@
|
||||
<string name="glasses_vr_fov">Campo de visión de SteamVR</string>
|
||||
<string name="glasses_vr_fov_hint">Más bajo acerca SteamVR y llena las gafas; más alto muestra más a tu alrededor.</string>
|
||||
<string name="glasses_playing">Jugando en %1$s</string>
|
||||
<string name="glasses_phone_recenter">Recentrar</string>
|
||||
<string name="glasses_phone_steamvr_menu">Menú de SteamVR</string>
|
||||
<string name="glasses_phone_switch_sticks">Sticks: manos ⇄ juego</string>
|
||||
<string name="glasses_phone_controllers">Mandos: %1$s</string>
|
||||
<string name="glasses_phone_keyboard">Teclado</string>
|
||||
<string name="glasses_phone_session_menu">Menú de sesión</string>
|
||||
<string name="glasses_phone_stop_steamvr">Detener SteamVR</string>
|
||||
<string name="glasses_phone_brightness">Brillo %1$d / %2$d</string>
|
||||
<string name="glasses_phone_battery">Batería %1$d%% · %2$d °C</string>
|
||||
<string name="glasses_calibration">Calibración del seguimiento</string>
|
||||
<string name="glasses_calibration_hint">Reduce la deriva y alinea la referencia horizontal.</string>
|
||||
<string name="glasses_calibration_saved_hint">Estas gafas usan la calibración guardada.</string>
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
<string name="gesture_click_once">Napsauta kerran</string>
|
||||
<string name="gesture_hold_delay">Pidon viive</string>
|
||||
<string name="gesture_double_delay">Kaksoisnapautuksen aikaväli</string>
|
||||
<string name="gesture_scope_game">Koskee nykyistä peliä</string>
|
||||
<string name="gesture_scope_default">Kaikkien pelien oletusasetukset</string>
|
||||
<string name="gesture_reset">Palauta alkuperäiset eleasetukset</string>
|
||||
<string name="gesture_cancel">Peruuta</string>
|
||||
<string name="gesture_action">Valitse toiminto</string>
|
||||
@@ -35,16 +37,23 @@
|
||||
<string name="gesture_scroll">Hiiren rulla</string>
|
||||
<string name="gesture_threshold">Elekynnys: %1$d px</string>
|
||||
<string name="gesture_debug">Näytä tunnistettu ele</string>
|
||||
<string name="gesture_editor_subtitle">Hiiri- ja näppäimistöohjaus eleittäin</string>
|
||||
<string name="gesture_tab_one">Yksi sormi</string>
|
||||
<string name="gesture_tab_two">Kaksi sormea</string>
|
||||
<string name="gesture_tab_three">Kolme sormea</string>
|
||||
<string name="gesture_tab_options">Asetukset</string>
|
||||
<string name="gesture_one_hint">Valitse napautuksen, vedon tai pidon toiminto.</string>
|
||||
<string name="gesture_two_hint">Määritä kameran panorointi, zoomaus ja toissijaiset napsautukset.</string>
|
||||
<string name="gesture_three_hint">Pidä lisäpikakomennot ulottuvillasi.</string>
|
||||
<string name="gesture_options_hint">Säädä liikettä, tunnistusta ja visuaalista palautetta.</string>
|
||||
<string name="gesture_defaults">Oletukset</string>
|
||||
<string name="gesture_game">Tämä peli</string>
|
||||
<string name="gesture_use_defaults">Käytä oletuksia</string>
|
||||
<string name="gesture_inheriting">Oletusasetukset ovat käytössä. Muokkaaminen luo pelikohtaiset asetukset.</string>
|
||||
<string name="gesture_save_changes">Tallenna muutokset</string>
|
||||
<string name="gesture_close">Sulje</string>
|
||||
<string name="gesture_action_label">TOIMINTO</string>
|
||||
<string name="gesture_action_for">Kohteelle %1$s</string>
|
||||
<string name="gesture_search_actions">Etsi hiiritoimintoja tai näppäimiä</string>
|
||||
<string name="gesture_clear_search">Tyhjennä haku</string>
|
||||
<string name="gesture_all_actions">Kaikki</string>
|
||||
@@ -59,9 +68,12 @@
|
||||
<string name="gesture_group_numpad">Numeronäppäimistö</string>
|
||||
<string name="gesture_group_symbols">Symbolit</string>
|
||||
<string name="gesture_movement">Hiiren liike</string>
|
||||
<string name="gesture_movement_hint">Suora veto seuraa kosketuskohtaa. Suhteellinen veto seuraa sormen liikettä ja sopii peleihin, jotka keskittävät hiiren.</string>
|
||||
<string name="gesture_middle_relative_hint">Panorointi keskipainikkeella seuraa aina sormen liikettä, jotta osoitin ei hypi.</string>
|
||||
<string name="gesture_direct">Suora</string>
|
||||
<string name="gesture_relative">Suhteellinen</string>
|
||||
<string name="gesture_recognition">Eleiden tunnistus</string>
|
||||
<string name="gesture_threshold_hint">Liikuta sormia tämän matkan verran, ennen kuin kahden tai kolmen sormen ele muuttuu panoroinniksi tai zoomaukseksi.</string>
|
||||
<string name="gesture_feedback">Visuaalinen palaute</string>
|
||||
<string name="gesture_mouse_behavior">Hiiripainikkeen toiminta</string>
|
||||
<string name="gesture_ms">%1$d ms</string>
|
||||
|
||||
@@ -582,16 +582,6 @@
|
||||
<string name="drawer_prev_page">Edellinen sivu</string>
|
||||
<string name="drawer_next_page">Seuraava sivu</string>
|
||||
<string name="drawer_hud">Suorituskyky-HUD</string>
|
||||
<string name="drawer_hud_fps">Kuvataajuus</string>
|
||||
<string name="drawer_hud_battery">Akun lämpötila</string>
|
||||
<string name="drawer_hud_graph">Suorituskykykäyrä</string>
|
||||
<string name="drawer_hud_color">Väri</string>
|
||||
<string name="drawer_hud_color_mint">Minttu</string>
|
||||
<string name="drawer_hud_color_white">Valkoinen</string>
|
||||
<string name="drawer_hud_color_sky">Taivaansininen</string>
|
||||
<string name="drawer_hud_color_amber">Meripihka</string>
|
||||
<string name="drawer_hud_color_violet">Violetti</string>
|
||||
<string name="drawer_hud_opacity">Peittävyys</string>
|
||||
<string name="drawer_scaling">Skaalaustila</string>
|
||||
<string name="drawer_scaling_sharpness">Skaalauksen terävyys</string>
|
||||
<string name="drawer_effects">Näyttötehosteet</string>
|
||||
@@ -2036,9 +2026,6 @@
|
||||
<string name="cloud_last_sync">Viimeisin synkronointi %1$s</string>
|
||||
<string name="cloud_never">Ei synkronoitu vielä</string>
|
||||
<string name="cloud_done">%1$d tiedostoa</string>
|
||||
<string name="cloud_up_to_date">Jo ajan tasalla</string>
|
||||
<string name="cloud_signed_out">Kirjaudu kauppaan synkronoidaksesi tallennukset</string>
|
||||
<string name="cloud_not_synced">Tallennuksia ei synkronoitu</string>
|
||||
<string name="stores_eyebrow_installed">asennettu</string>
|
||||
<string name="stores_eyebrow_library">kirjastossasi</string>
|
||||
<string name="stores_eyebrow_store">kauppa</string>
|
||||
@@ -2342,7 +2329,7 @@
|
||||
<string name="touch_done">Valmis</string>
|
||||
<string name="touch_save_failed">Kosketusasettelua ei voitu tallentaa</string>
|
||||
<string name="touch_saved">Kosketusasettelu tallennettu Steamiin</string>
|
||||
<string name="touch_app_id">Sovellus %1$s</string>
|
||||
<string name="touch_app_id">Sovellus %1$d</string>
|
||||
<string name="touch_label_hint">Nimike (valinnainen)</string>
|
||||
<string name="touch_icons_missing">Steamin kuvakkeita ei löytynyt</string>
|
||||
<plurals name="touch_icons_found">
|
||||
@@ -2433,15 +2420,6 @@
|
||||
<string name="glasses_vr_fov">SteamVR:n näkökenttä</string>
|
||||
<string name="glasses_vr_fov_hint">Pienempi tuo SteamVR:n lähemmäs ja täyttää lasit; suurempi näyttää enemmän ympäriltäsi.</string>
|
||||
<string name="glasses_playing">Pelataan laitteella %1$s</string>
|
||||
<string name="glasses_phone_recenter">Keskitä uudelleen</string>
|
||||
<string name="glasses_phone_steamvr_menu">SteamVR-valikko</string>
|
||||
<string name="glasses_phone_switch_sticks">Sauvat: kädet ⇄ peli</string>
|
||||
<string name="glasses_phone_controllers">Ohjaimet: %1$s</string>
|
||||
<string name="glasses_phone_keyboard">Näppäimistö</string>
|
||||
<string name="glasses_phone_session_menu">Istuntovalikko</string>
|
||||
<string name="glasses_phone_stop_steamvr">Pysäytä SteamVR</string>
|
||||
<string name="glasses_phone_brightness">Kirkkaus %1$d / %2$d</string>
|
||||
<string name="glasses_phone_battery">Akku %1$d%% · %2$d °C</string>
|
||||
<string name="glasses_calibration">Seurannan kalibrointi</string>
|
||||
<string name="glasses_calibration_hint">Vähentää ryömintää ja kohdistaa vaakatason.</string>
|
||||
<string name="glasses_calibration_saved_hint">Tallennettu kalibrointi on käytössä näissä laseissa.</string>
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
<string name="gesture_click_once">Cliquer une fois</string>
|
||||
<string name="gesture_hold_delay">Délai de maintien</string>
|
||||
<string name="gesture_double_delay">Intervalle du double appui</string>
|
||||
<string name="gesture_scope_game">S\'applique au jeu actuel</string>
|
||||
<string name="gesture_scope_default">Paramètres par défaut pour tous les jeux</string>
|
||||
<string name="gesture_reset">Rétablir les paramètres des gestes d\'origine</string>
|
||||
<string name="gesture_cancel">Annuler</string>
|
||||
<string name="gesture_action">Choisir une action</string>
|
||||
@@ -35,16 +37,23 @@
|
||||
<string name="gesture_scroll">Molette de la souris</string>
|
||||
<string name="gesture_threshold">Seuil des gestes : %1$d px</string>
|
||||
<string name="gesture_debug">Afficher le geste reconnu</string>
|
||||
<string name="gesture_editor_subtitle">Commandes de souris et de clavier organisées par geste</string>
|
||||
<string name="gesture_tab_one">Un doigt</string>
|
||||
<string name="gesture_tab_two">Deux doigts</string>
|
||||
<string name="gesture_tab_three">Trois doigts</string>
|
||||
<string name="gesture_tab_options">Options</string>
|
||||
<string name="gesture_one_hint">Choisissez l\'action d\'un appui, d\'un glissement ou d\'un maintien.</string>
|
||||
<string name="gesture_two_hint">Configurez les déplacements de caméra, le zoom et les clics secondaires.</string>
|
||||
<string name="gesture_three_hint">Gardez des raccourcis supplémentaires à portée de main.</string>
|
||||
<string name="gesture_options_hint">Ajustez les mouvements, la reconnaissance et le retour visuel.</string>
|
||||
<string name="gesture_defaults">Valeurs par défaut</string>
|
||||
<string name="gesture_game">Ce jeu</string>
|
||||
<string name="gesture_use_defaults">Utiliser les valeurs par défaut</string>
|
||||
<string name="gesture_inheriting">Paramètres par défaut utilisés. Une modification crée des paramètres propres au jeu.</string>
|
||||
<string name="gesture_save_changes">Enregistrer les modifications</string>
|
||||
<string name="gesture_close">Fermer</string>
|
||||
<string name="gesture_action_label">ACTION</string>
|
||||
<string name="gesture_action_for">Pour %1$s</string>
|
||||
<string name="gesture_search_actions">Rechercher des actions de souris ou des touches du clavier</string>
|
||||
<string name="gesture_clear_search">Effacer la recherche</string>
|
||||
<string name="gesture_all_actions">Tout</string>
|
||||
@@ -59,9 +68,12 @@
|
||||
<string name="gesture_group_numpad">Pavé numérique</string>
|
||||
<string name="gesture_group_symbols">Symboles</string>
|
||||
<string name="gesture_movement">Mouvement de la souris</string>
|
||||
<string name="gesture_movement_hint">Le glissement direct suit la position du toucher. Le glissement relatif suit le mouvement du doigt, utile lorsqu\'un jeu recentre la souris.</string>
|
||||
<string name="gesture_middle_relative_hint">Le déplacement avec le bouton du milieu suit toujours le mouvement du doigt pour éviter les sauts du curseur.</string>
|
||||
<string name="gesture_direct">Direct</string>
|
||||
<string name="gesture_relative">Relatif</string>
|
||||
<string name="gesture_recognition">Reconnaissance des gestes</string>
|
||||
<string name="gesture_threshold_hint">Parcourez cette distance avant qu\'un geste à deux ou trois doigts soit reconnu comme un déplacement ou un zoom.</string>
|
||||
<string name="gesture_feedback">Retour visuel</string>
|
||||
<string name="gesture_mouse_behavior">Comportement du bouton de souris</string>
|
||||
<string name="gesture_ms">%1$d ms</string>
|
||||
|
||||
@@ -581,16 +581,6 @@
|
||||
<string name="drawer_prev_page">Page précédente</string>
|
||||
<string name="drawer_next_page">Page suivante</string>
|
||||
<string name="drawer_hud">HUD de performance</string>
|
||||
<string name="drawer_hud_fps">Fréquence d\'images</string>
|
||||
<string name="drawer_hud_battery">Température de la batterie</string>
|
||||
<string name="drawer_hud_graph">Graphique de performance</string>
|
||||
<string name="drawer_hud_color">Couleur</string>
|
||||
<string name="drawer_hud_color_mint">Menthe</string>
|
||||
<string name="drawer_hud_color_white">Blanc</string>
|
||||
<string name="drawer_hud_color_sky">Ciel</string>
|
||||
<string name="drawer_hud_color_amber">Ambre</string>
|
||||
<string name="drawer_hud_color_violet">Violet</string>
|
||||
<string name="drawer_hud_opacity">Opacité</string>
|
||||
<string name="drawer_scaling">Mode de mise à l\'échelle</string>
|
||||
<string name="drawer_scaling_sharpness">Netteté de la mise à l\'échelle</string>
|
||||
<string name="drawer_effects">Effets d\'écran</string>
|
||||
@@ -2054,9 +2044,6 @@
|
||||
<string name="cloud_last_sync">Dernière synchro : %1$s</string>
|
||||
<string name="cloud_never">Pas encore synchronisé</string>
|
||||
<string name="cloud_done">%1$d fichiers</string>
|
||||
<string name="cloud_up_to_date">Déjà à jour</string>
|
||||
<string name="cloud_signed_out">Connectez-vous à la boutique pour synchroniser les sauvegardes</string>
|
||||
<string name="cloud_not_synced">Les sauvegardes n’ont pas été synchronisées</string>
|
||||
<string name="stores_eyebrow_installed">installé</string>
|
||||
<string name="stores_eyebrow_library">dans votre bibliothèque</string>
|
||||
<string name="stores_eyebrow_store">boutique</string>
|
||||
@@ -2359,7 +2346,7 @@
|
||||
<string name="touch_done">Terminé</string>
|
||||
<string name="touch_save_failed">Impossible d\'enregistrer la disposition tactile</string>
|
||||
<string name="touch_saved">Disposition tactile enregistrée sur Steam</string>
|
||||
<string name="touch_app_id">Appli %1$s</string>
|
||||
<string name="touch_app_id">Appli %1$d</string>
|
||||
<string name="touch_label_hint">Libellé (facultatif)</string>
|
||||
<string name="touch_icons_missing">Les icônes de Steam n\'ont pas été trouvées</string>
|
||||
<plurals name="touch_icons_found">
|
||||
@@ -2452,15 +2439,6 @@
|
||||
<string name="glasses_vr_fov">Champ de vision SteamVR</string>
|
||||
<string name="glasses_vr_fov_hint">Plus bas rapproche SteamVR et remplit les lunettes ; plus haut montre davantage autour de vous.</string>
|
||||
<string name="glasses_playing">En jeu sur %1$s</string>
|
||||
<string name="glasses_phone_recenter">Recentrer</string>
|
||||
<string name="glasses_phone_steamvr_menu">Menu SteamVR</string>
|
||||
<string name="glasses_phone_switch_sticks">Sticks : mains ⇄ jeu</string>
|
||||
<string name="glasses_phone_controllers">Manettes : %1$s</string>
|
||||
<string name="glasses_phone_keyboard">Clavier</string>
|
||||
<string name="glasses_phone_session_menu">Menu de session</string>
|
||||
<string name="glasses_phone_stop_steamvr">Arrêter SteamVR</string>
|
||||
<string name="glasses_phone_brightness">Luminosité %1$d / %2$d</string>
|
||||
<string name="glasses_phone_battery">Batterie %1$d%% · %2$d °C</string>
|
||||
<string name="glasses_calibration">Calibrage du suivi</string>
|
||||
<string name="glasses_calibration_hint">Réduit la dérive et aligne la référence horizontale.</string>
|
||||
<string name="glasses_calibration_saved_hint">Le calibrage enregistré est appliqué à ces lunettes.</string>
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
<string name="gesture_click_once">एक बार क्लिक करें</string>
|
||||
<string name="gesture_hold_delay">दबाए रखने का विलंब</string>
|
||||
<string name="gesture_double_delay">दो टैप के बीच का अंतराल</string>
|
||||
<string name="gesture_scope_game">मौजूदा गेम पर लागू होता है</string>
|
||||
<string name="gesture_scope_default">सभी गेम की डिफ़ॉल्ट सेटिंग</string>
|
||||
<string name="gesture_reset">जेस्चर की मूल सेटिंग वापस लाएँ</string>
|
||||
<string name="gesture_cancel">रद्द करें</string>
|
||||
<string name="gesture_action">कार्रवाई चुनें</string>
|
||||
@@ -35,16 +37,23 @@
|
||||
<string name="gesture_scroll">माउस व्हील</string>
|
||||
<string name="gesture_threshold">जेस्चर की सीमा: %1$d पिक्सेल</string>
|
||||
<string name="gesture_debug">पहचाना गया जेस्चर दिखाएँ</string>
|
||||
<string name="gesture_editor_subtitle">जेस्चर के अनुसार माउस और कीबोर्ड कंट्रोल</string>
|
||||
<string name="gesture_tab_one">एक उँगली</string>
|
||||
<string name="gesture_tab_two">दो उँगलियाँ</string>
|
||||
<string name="gesture_tab_three">तीन उँगलियाँ</string>
|
||||
<string name="gesture_tab_options">विकल्प</string>
|
||||
<string name="gesture_one_hint">चुनें कि टैप करने, खींचने या दबाए रखने पर क्या हो।</string>
|
||||
<string name="gesture_two_hint">कैमरा खिसकाना, ज़ूम और दूसरे क्लिक सेट करें।</string>
|
||||
<string name="gesture_three_hint">अतिरिक्त शॉर्टकट आसानी से इस्तेमाल करें।</string>
|
||||
<string name="gesture_options_hint">गति, पहचान और दृश्य प्रतिक्रिया में बदलाव करें।</string>
|
||||
<string name="gesture_defaults">डिफ़ॉल्ट</string>
|
||||
<string name="gesture_game">यह गेम</string>
|
||||
<string name="gesture_use_defaults">डिफ़ॉल्ट इस्तेमाल करें</string>
|
||||
<string name="gesture_inheriting">डिफ़ॉल्ट सेटिंग इस्तेमाल हो रही हैं। बदलाव करने पर इस गेम की अलग सेटिंग बनेंगी।</string>
|
||||
<string name="gesture_save_changes">बदलाव सेव करें</string>
|
||||
<string name="gesture_close">बंद करें</string>
|
||||
<string name="gesture_action_label">कार्रवाई</string>
|
||||
<string name="gesture_action_for">%1$s के लिए</string>
|
||||
<string name="gesture_search_actions">माउस की कार्रवाइयाँ या कीबोर्ड की कुंजियाँ खोजें</string>
|
||||
<string name="gesture_clear_search">खोज मिटाएँ</string>
|
||||
<string name="gesture_all_actions">सभी</string>
|
||||
@@ -59,9 +68,12 @@
|
||||
<string name="gesture_group_numpad">नंबर पैड</string>
|
||||
<string name="gesture_group_symbols">चिह्न</string>
|
||||
<string name="gesture_movement">माउस की गति</string>
|
||||
<string name="gesture_movement_hint">सीधे खींचना छुई गई जगह के अनुसार चलता है। सापेक्ष खींचना उँगली की गति के अनुसार चलता है और गेम के माउस को बीच में लाने पर उपयोगी है।</string>
|
||||
<string name="gesture_middle_relative_hint">माउस के बीच वाले बटन से दृश्य खिसकाना हमेशा उँगली की गति के अनुसार चलता है, ताकि कर्सर न उछले।</string>
|
||||
<string name="gesture_direct">डायरेक्ट</string>
|
||||
<string name="gesture_relative">सापेक्ष</string>
|
||||
<string name="gesture_recognition">जेस्चर की पहचान</string>
|
||||
<string name="gesture_threshold_hint">दो या तीन उँगलियों का जेस्चर दृश्य खिसकाने या ज़ूम में बदलने से पहले इतनी दूरी तय करें।</string>
|
||||
<string name="gesture_feedback">दृश्य प्रतिक्रिया</string>
|
||||
<string name="gesture_mouse_behavior">माउस बटन का व्यवहार</string>
|
||||
<string name="gesture_ms">%1$d मिलीसेकंड</string>
|
||||
|
||||
@@ -582,16 +582,6 @@
|
||||
<string name="drawer_prev_page">पिछला पेज</string>
|
||||
<string name="drawer_next_page">अगला पेज</string>
|
||||
<string name="drawer_hud">परफ़ॉर्मेंस HUD</string>
|
||||
<string name="drawer_hud_fps">फ़्रेम दर</string>
|
||||
<string name="drawer_hud_battery">बैटरी तापमान</string>
|
||||
<string name="drawer_hud_graph">परफ़ॉर्मेंस ग्राफ़</string>
|
||||
<string name="drawer_hud_color">रंग</string>
|
||||
<string name="drawer_hud_color_mint">मिंट</string>
|
||||
<string name="drawer_hud_color_white">सफ़ेद</string>
|
||||
<string name="drawer_hud_color_sky">स्काई</string>
|
||||
<string name="drawer_hud_color_amber">एम्बर</string>
|
||||
<string name="drawer_hud_color_violet">वायलेट</string>
|
||||
<string name="drawer_hud_opacity">ओपैसिटी</string>
|
||||
<string name="drawer_scaling">स्केलिंग मोड</string>
|
||||
<string name="drawer_scaling_sharpness">स्केलिंग शार्पनेस</string>
|
||||
<string name="drawer_effects">स्क्रीन इफ़ेक्ट</string>
|
||||
@@ -2036,9 +2026,6 @@
|
||||
<string name="cloud_last_sync">आख़िरी सिंक %1$s</string>
|
||||
<string name="cloud_never">अभी सिंक नहीं हुआ</string>
|
||||
<string name="cloud_done">%1$d फ़ाइलें</string>
|
||||
<string name="cloud_up_to_date">पहले से अप टू डेट</string>
|
||||
<string name="cloud_signed_out">सेव सिंक करने के लिए स्टोर में साइन इन करें</string>
|
||||
<string name="cloud_not_synced">सेव सिंक नहीं हुए</string>
|
||||
<string name="stores_eyebrow_installed">इंस्टॉल है</string>
|
||||
<string name="stores_eyebrow_library">आपकी लाइब्रेरी में</string>
|
||||
<string name="stores_eyebrow_store">स्टोर</string>
|
||||
@@ -2342,7 +2329,7 @@
|
||||
<string name="touch_done">हो गया</string>
|
||||
<string name="touch_save_failed">टच लेआउट सेव नहीं हो सका</string>
|
||||
<string name="touch_saved">टच लेआउट Steam में सेव हो गया</string>
|
||||
<string name="touch_app_id">ऐप %1$s</string>
|
||||
<string name="touch_app_id">ऐप %1$d</string>
|
||||
<string name="touch_label_hint">लेबल (वैकल्पिक)</string>
|
||||
<string name="touch_icons_missing">Steam के आइकॉन नहीं मिले</string>
|
||||
<plurals name="touch_icons_found">
|
||||
@@ -2433,15 +2420,6 @@
|
||||
<string name="glasses_vr_fov">SteamVR फ़ील्ड ऑफ़ व्यू</string>
|
||||
<string name="glasses_vr_fov_hint">कम करने पर SteamVR पास आता है और चश्मा भर देता है; ज़्यादा करने पर आपके आस-पास ज़्यादा दिखता है।</string>
|
||||
<string name="glasses_playing">%1$s पर खेल रहे हैं</string>
|
||||
<string name="glasses_phone_recenter">फिर से केंद्रित करें</string>
|
||||
<string name="glasses_phone_steamvr_menu">SteamVR मेनू</string>
|
||||
<string name="glasses_phone_switch_sticks">स्टिक: हाथ ⇄ गेम</string>
|
||||
<string name="glasses_phone_controllers">कंट्रोलर: %1$s</string>
|
||||
<string name="glasses_phone_keyboard">कीबोर्ड</string>
|
||||
<string name="glasses_phone_session_menu">सत्र मेनू</string>
|
||||
<string name="glasses_phone_stop_steamvr">SteamVR रोकें</string>
|
||||
<string name="glasses_phone_brightness">चमक %1$d / %2$d</string>
|
||||
<string name="glasses_phone_battery">बैटरी %1$d%% · %2$d °C</string>
|
||||
<string name="glasses_calibration">ट्रैकिंग कैलिब्रेशन</string>
|
||||
<string name="glasses_calibration_hint">ड्रिफ़्ट कम करता है और क्षैतिज संदर्भ को सीध में लाता है।</string>
|
||||
<string name="glasses_calibration_saved_hint">इस चश्मे पर सेव किया गया कैलिब्रेशन लागू है।</string>
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
<string name="gesture_click_once">Egyszeri kattintás</string>
|
||||
<string name="gesture_hold_delay">Nyomva tartás késleltetése</string>
|
||||
<string name="gesture_double_delay">Dupla koppintás időköze</string>
|
||||
<string name="gesture_scope_game">Az aktuális játékra érvényes</string>
|
||||
<string name="gesture_scope_default">Alapbeállítások minden játékhoz</string>
|
||||
<string name="gesture_reset">Eredeti gesztusbeállítások visszaállítása</string>
|
||||
<string name="gesture_cancel">Mégse</string>
|
||||
<string name="gesture_action">Művelet kiválasztása</string>
|
||||
@@ -35,16 +37,23 @@
|
||||
<string name="gesture_scroll">Egérgörgő</string>
|
||||
<string name="gesture_threshold">Gesztusküszöb: %1$d px</string>
|
||||
<string name="gesture_debug">Felismert gesztus megjelenítése</string>
|
||||
<string name="gesture_editor_subtitle">Egér- és billentyűzetvezérlés gesztusok szerint</string>
|
||||
<string name="gesture_tab_one">Egy ujj</string>
|
||||
<string name="gesture_tab_two">Két ujj</string>
|
||||
<string name="gesture_tab_three">Három ujj</string>
|
||||
<string name="gesture_tab_options">Beállítások</string>
|
||||
<string name="gesture_one_hint">Válaszd ki a koppintás, húzás vagy nyomva tartás műveletét.</string>
|
||||
<string name="gesture_two_hint">Állítsd be a kamera pásztázását, a nagyítást és a másodlagos kattintásokat.</string>
|
||||
<string name="gesture_three_hint">Tarts további gyorsparancsokat kéznél.</string>
|
||||
<string name="gesture_options_hint">Finomhangold a mozgást, a felismerést és a vizuális visszajelzést.</string>
|
||||
<string name="gesture_defaults">Alapértelmezések</string>
|
||||
<string name="gesture_game">Ez a játék</string>
|
||||
<string name="gesture_use_defaults">Alapbeállítások használata</string>
|
||||
<string name="gesture_inheriting">Az alapbeállítások érvényesek. A szerkesztés játékhoz tartozó beállításokat hoz létre.</string>
|
||||
<string name="gesture_save_changes">Módosítások mentése</string>
|
||||
<string name="gesture_close">Bezárás</string>
|
||||
<string name="gesture_action_label">MŰVELET</string>
|
||||
<string name="gesture_action_for">Ehhez: %1$s</string>
|
||||
<string name="gesture_search_actions">Egérműveletek vagy billentyűk keresése</string>
|
||||
<string name="gesture_clear_search">Keresés törlése</string>
|
||||
<string name="gesture_all_actions">Összes</string>
|
||||
@@ -59,9 +68,12 @@
|
||||
<string name="gesture_group_numpad">Numerikus billentyűzet</string>
|
||||
<string name="gesture_group_symbols">Szimbólumok</string>
|
||||
<string name="gesture_movement">Egérmozgás</string>
|
||||
<string name="gesture_movement_hint">A közvetlen húzás az érintési helyet követi. A relatív húzás az ujj mozgását követi, ami hasznos, ha a játék középre állítja az egeret.</string>
|
||||
<string name="gesture_middle_relative_hint">A középső egérgombos pásztázás mindig az ujj mozgását követi, hogy a kurzor ne ugorjon el.</string>
|
||||
<string name="gesture_direct">Közvetlen</string>
|
||||
<string name="gesture_relative">Relatív</string>
|
||||
<string name="gesture_recognition">Gesztusfelismerés</string>
|
||||
<string name="gesture_threshold_hint">Ennyit kell elmozdítani az ujjakat, mielőtt a két- vagy háromujjas gesztus pásztázássá vagy nagyítássá válik.</string>
|
||||
<string name="gesture_feedback">Vizuális visszajelzés</string>
|
||||
<string name="gesture_mouse_behavior">Egérgomb működése</string>
|
||||
<string name="gesture_ms">%1$d ms</string>
|
||||
|
||||
@@ -582,16 +582,6 @@
|
||||
<string name="drawer_prev_page">Előző oldal</string>
|
||||
<string name="drawer_next_page">Következő oldal</string>
|
||||
<string name="drawer_hud">Teljesítmény HUD</string>
|
||||
<string name="drawer_hud_fps">Képkockasebesség</string>
|
||||
<string name="drawer_hud_battery">Akkuhőmérséklet</string>
|
||||
<string name="drawer_hud_graph">Teljesítménygrafikon</string>
|
||||
<string name="drawer_hud_color">Szín</string>
|
||||
<string name="drawer_hud_color_mint">Menta</string>
|
||||
<string name="drawer_hud_color_white">Fehér</string>
|
||||
<string name="drawer_hud_color_sky">Égszínkék</string>
|
||||
<string name="drawer_hud_color_amber">Borostyán</string>
|
||||
<string name="drawer_hud_color_violet">Lila</string>
|
||||
<string name="drawer_hud_opacity">Átlátszatlanság</string>
|
||||
<string name="drawer_scaling">Méretezési mód</string>
|
||||
<string name="drawer_scaling_sharpness">Méretezési élesség</string>
|
||||
<string name="drawer_effects">Képernyőeffektek</string>
|
||||
@@ -2036,9 +2026,6 @@
|
||||
<string name="cloud_last_sync">Utolsó szinkronizálás: %1$s</string>
|
||||
<string name="cloud_never">Még nincs szinkronizálva</string>
|
||||
<string name="cloud_done">%1$d fájl</string>
|
||||
<string name="cloud_up_to_date">Már naprakész</string>
|
||||
<string name="cloud_signed_out">Jelentkezz be az áruházba a mentések szinkronizálásához</string>
|
||||
<string name="cloud_not_synced">A mentések nem lettek szinkronizálva</string>
|
||||
<string name="stores_eyebrow_installed">telepítve</string>
|
||||
<string name="stores_eyebrow_library">a könyvtárában</string>
|
||||
<string name="stores_eyebrow_store">áruház</string>
|
||||
@@ -2342,7 +2329,7 @@
|
||||
<string name="touch_done">Kész</string>
|
||||
<string name="touch_save_failed">Az érintőelrendezést nem sikerült elmenteni</string>
|
||||
<string name="touch_saved">Az érintőelrendezés elmentve a Steambe</string>
|
||||
<string name="touch_app_id">%1$s. alkalmazás</string>
|
||||
<string name="touch_app_id">%1$d. alkalmazás</string>
|
||||
<string name="touch_label_hint">Felirat (nem kötelező)</string>
|
||||
<string name="touch_icons_missing">A Steam ikonjai nem találhatók</string>
|
||||
<plurals name="touch_icons_found">
|
||||
@@ -2433,15 +2420,6 @@
|
||||
<string name="glasses_vr_fov">SteamVR látómező</string>
|
||||
<string name="glasses_vr_fov_hint">Kisebb érték közelebb hozza a SteamVR-t és kitölti a szemüveget; nagyobb többet mutat a környezetéből.</string>
|
||||
<string name="glasses_playing">Játék ezen: %1$s</string>
|
||||
<string name="glasses_phone_recenter">Középre igazítás</string>
|
||||
<string name="glasses_phone_steamvr_menu">SteamVR menü</string>
|
||||
<string name="glasses_phone_switch_sticks">Karok: kezek ⇄ játék</string>
|
||||
<string name="glasses_phone_controllers">Kontrollerek: %1$s</string>
|
||||
<string name="glasses_phone_keyboard">Billentyűzet</string>
|
||||
<string name="glasses_phone_session_menu">Munkamenet menü</string>
|
||||
<string name="glasses_phone_stop_steamvr">SteamVR leállítása</string>
|
||||
<string name="glasses_phone_brightness">Fényerő %1$d / %2$d</string>
|
||||
<string name="glasses_phone_battery">Akkumulátor %1$d%% · %2$d °C</string>
|
||||
<string name="glasses_calibration">Követés kalibrálása</string>
|
||||
<string name="glasses_calibration_hint">Csökkenti a sodródást, és beállítja a vízszintes referenciát.</string>
|
||||
<string name="glasses_calibration_saved_hint">A szemüveg a mentett kalibrációt használja.</string>
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
<string name="gesture_click_once">Klik sekali</string>
|
||||
<string name="gesture_hold_delay">Jeda sebelum menahan</string>
|
||||
<string name="gesture_double_delay">Interval ketuk dua kali</string>
|
||||
<string name="gesture_scope_game">Berlaku untuk game saat ini</string>
|
||||
<string name="gesture_scope_default">Setelan default untuk semua game</string>
|
||||
<string name="gesture_reset">Pulihkan setelan gestur awal</string>
|
||||
<string name="gesture_cancel">Batal</string>
|
||||
<string name="gesture_action">Pilih tindakan</string>
|
||||
@@ -35,16 +37,23 @@
|
||||
<string name="gesture_scroll">Roda mouse</string>
|
||||
<string name="gesture_threshold">Ambang gestur: %1$d px</string>
|
||||
<string name="gesture_debug">Tampilkan gestur yang dikenali</string>
|
||||
<string name="gesture_editor_subtitle">Kontrol mouse dan keyboard menurut gestur</string>
|
||||
<string name="gesture_tab_one">Satu jari</string>
|
||||
<string name="gesture_tab_two">Dua jari</string>
|
||||
<string name="gesture_tab_three">Tiga jari</string>
|
||||
<string name="gesture_tab_options">Opsi</string>
|
||||
<string name="gesture_one_hint">Pilih tindakan saat mengetuk, menyeret, atau menahan.</string>
|
||||
<string name="gesture_two_hint">Atur pergeseran kamera, zoom, dan klik sekunder.</string>
|
||||
<string name="gesture_three_hint">Jangkau lebih banyak pintasan dengan mudah.</string>
|
||||
<string name="gesture_options_hint">Sesuaikan gerakan, pengenalan, dan umpan balik visual.</string>
|
||||
<string name="gesture_defaults">Default</string>
|
||||
<string name="gesture_game">Game ini</string>
|
||||
<string name="gesture_use_defaults">Gunakan default</string>
|
||||
<string name="gesture_inheriting">Menggunakan setelan default. Mengedit akan membuat setelan khusus game.</string>
|
||||
<string name="gesture_save_changes">Simpan perubahan</string>
|
||||
<string name="gesture_close">Tutup</string>
|
||||
<string name="gesture_action_label">TINDAKAN</string>
|
||||
<string name="gesture_action_for">Untuk %1$s</string>
|
||||
<string name="gesture_search_actions">Cari tindakan mouse atau tombol keyboard</string>
|
||||
<string name="gesture_clear_search">Hapus pencarian</string>
|
||||
<string name="gesture_all_actions">Semua</string>
|
||||
@@ -59,9 +68,12 @@
|
||||
<string name="gesture_group_numpad">Keypad numerik</string>
|
||||
<string name="gesture_group_symbols">Simbol</string>
|
||||
<string name="gesture_movement">Gerakan mouse</string>
|
||||
<string name="gesture_movement_hint">Seret langsung mengikuti posisi sentuhan. Seret relatif mengikuti gerakan jari, berguna saat game memusatkan mouse.</string>
|
||||
<string name="gesture_middle_relative_hint">Pergeseran dengan tombol tengah mouse selalu mengikuti gerakan jari agar kursor tidak melompat.</string>
|
||||
<string name="gesture_direct">Langsung</string>
|
||||
<string name="gesture_relative">Relatif</string>
|
||||
<string name="gesture_recognition">Pengenalan gestur</string>
|
||||
<string name="gesture_threshold_hint">Gerakkan sejauh ini sebelum gestur dua atau tiga jari menjadi pergeseran atau zoom.</string>
|
||||
<string name="gesture_feedback">Umpan balik visual</string>
|
||||
<string name="gesture_mouse_behavior">Perilaku tombol mouse</string>
|
||||
<string name="gesture_ms">%1$d md</string>
|
||||
|
||||
@@ -580,16 +580,6 @@
|
||||
<string name="drawer_prev_page">Halaman sebelumnya</string>
|
||||
<string name="drawer_next_page">Halaman berikutnya</string>
|
||||
<string name="drawer_hud">HUD performa</string>
|
||||
<string name="drawer_hud_fps">Laju bingkai</string>
|
||||
<string name="drawer_hud_battery">Suhu baterai</string>
|
||||
<string name="drawer_hud_graph">Grafik performa</string>
|
||||
<string name="drawer_hud_color">Warna</string>
|
||||
<string name="drawer_hud_color_mint">Mint</string>
|
||||
<string name="drawer_hud_color_white">Putih</string>
|
||||
<string name="drawer_hud_color_sky">Langit</string>
|
||||
<string name="drawer_hud_color_amber">Ambar</string>
|
||||
<string name="drawer_hud_color_violet">Ungu</string>
|
||||
<string name="drawer_hud_opacity">Opasitas</string>
|
||||
<string name="drawer_scaling">Mode penskalaan</string>
|
||||
<string name="drawer_scaling_sharpness">Ketajaman penskalaan</string>
|
||||
<string name="drawer_effects">Efek layar</string>
|
||||
@@ -2015,9 +2005,6 @@
|
||||
<string name="cloud_last_sync">Sinkronisasi terakhir %1$s</string>
|
||||
<string name="cloud_never">Belum disinkronkan</string>
|
||||
<string name="cloud_done">%1$d file</string>
|
||||
<string name="cloud_up_to_date">Sudah terbaru</string>
|
||||
<string name="cloud_signed_out">Masuk ke toko untuk menyinkronkan simpanan</string>
|
||||
<string name="cloud_not_synced">Simpanan tidak disinkronkan</string>
|
||||
<string name="stores_eyebrow_installed">terinstal</string>
|
||||
<string name="stores_eyebrow_library">dalam pustaka Anda</string>
|
||||
<string name="stores_eyebrow_store">toko</string>
|
||||
@@ -2319,7 +2306,7 @@
|
||||
<string name="touch_done">Selesai</string>
|
||||
<string name="touch_save_failed">Tata letak sentuh tidak dapat disimpan</string>
|
||||
<string name="touch_saved">Tata letak sentuh disimpan ke Steam</string>
|
||||
<string name="touch_app_id">App %1$s</string>
|
||||
<string name="touch_app_id">App %1$d</string>
|
||||
<string name="touch_label_hint">Label (opsional)</string>
|
||||
<string name="touch_icons_missing">Ikon Steam tidak ditemukan</string>
|
||||
<plurals name="touch_icons_found">
|
||||
@@ -2408,15 +2395,6 @@
|
||||
<string name="glasses_vr_fov">Bidang pandang SteamVR</string>
|
||||
<string name="glasses_vr_fov_hint">Lebih rendah membawa SteamVR lebih dekat dan memenuhi kacamata; lebih tinggi menampilkan lebih banyak di sekitar Anda.</string>
|
||||
<string name="glasses_playing">Bermain di %1$s</string>
|
||||
<string name="glasses_phone_recenter">Pusatkan ulang</string>
|
||||
<string name="glasses_phone_steamvr_menu">Menu SteamVR</string>
|
||||
<string name="glasses_phone_switch_sticks">Stik: tangan ⇄ game</string>
|
||||
<string name="glasses_phone_controllers">Kontroler: %1$s</string>
|
||||
<string name="glasses_phone_keyboard">Keyboard</string>
|
||||
<string name="glasses_phone_session_menu">Menu sesi</string>
|
||||
<string name="glasses_phone_stop_steamvr">Hentikan SteamVR</string>
|
||||
<string name="glasses_phone_brightness">Kecerahan %1$d / %2$d</string>
|
||||
<string name="glasses_phone_battery">Baterai %1$d%% · %2$d °C</string>
|
||||
<string name="glasses_calibration">Kalibrasi pelacakan</string>
|
||||
<string name="glasses_calibration_hint">Mengurangi drift dan menyelaraskan acuan horizontal.</string>
|
||||
<string name="glasses_calibration_saved_hint">Kalibrasi tersimpan diterapkan ke kacamata ini.</string>
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
<string name="gesture_click_once">Clic singolo</string>
|
||||
<string name="gesture_hold_delay">Ritardo della pressione</string>
|
||||
<string name="gesture_double_delay">Intervallo del doppio tocco</string>
|
||||
<string name="gesture_scope_game">Si applica al gioco attuale</string>
|
||||
<string name="gesture_scope_default">Impostazioni predefinite per tutti i giochi</string>
|
||||
<string name="gesture_reset">Ripristina le impostazioni originali dei gesti</string>
|
||||
<string name="gesture_cancel">Annulla</string>
|
||||
<string name="gesture_action">Scegli azione</string>
|
||||
@@ -35,16 +37,23 @@
|
||||
<string name="gesture_scroll">Rotellina del mouse</string>
|
||||
<string name="gesture_threshold">Soglia dei gesti: %1$d px</string>
|
||||
<string name="gesture_debug">Mostra il gesto riconosciuto</string>
|
||||
<string name="gesture_editor_subtitle">Comandi di mouse e tastiera organizzati per gesto</string>
|
||||
<string name="gesture_tab_one">Un dito</string>
|
||||
<string name="gesture_tab_two">Due dita</string>
|
||||
<string name="gesture_tab_three">Tre dita</string>
|
||||
<string name="gesture_tab_options">Opzioni</string>
|
||||
<string name="gesture_one_hint">Scegli l\'azione di un tocco, trascinamento o pressione.</string>
|
||||
<string name="gesture_two_hint">Configura lo spostamento della visuale, lo zoom e i clic secondari.</string>
|
||||
<string name="gesture_three_hint">Tieni altre scorciatoie a portata di mano.</string>
|
||||
<string name="gesture_options_hint">Regola movimento, riconoscimento e riscontro visivo.</string>
|
||||
<string name="gesture_defaults">Predefiniti</string>
|
||||
<string name="gesture_game">Questo gioco</string>
|
||||
<string name="gesture_use_defaults">Usa impostazioni predefinite</string>
|
||||
<string name="gesture_inheriting">Sono in uso le impostazioni predefinite. Una modifica crea impostazioni specifiche del gioco.</string>
|
||||
<string name="gesture_save_changes">Salva modifiche</string>
|
||||
<string name="gesture_close">Chiudi</string>
|
||||
<string name="gesture_action_label">AZIONE</string>
|
||||
<string name="gesture_action_for">Per %1$s</string>
|
||||
<string name="gesture_search_actions">Cerca azioni del mouse o tasti della tastiera</string>
|
||||
<string name="gesture_clear_search">Cancella ricerca</string>
|
||||
<string name="gesture_all_actions">Tutte</string>
|
||||
@@ -59,9 +68,12 @@
|
||||
<string name="gesture_group_numpad">Tastierino numerico</string>
|
||||
<string name="gesture_group_symbols">Simboli</string>
|
||||
<string name="gesture_movement">Movimento del mouse</string>
|
||||
<string name="gesture_movement_hint">Il trascinamento diretto segue la posizione del tocco. Quello relativo segue il movimento del dito ed è utile quando un gioco ricentra il mouse.</string>
|
||||
<string name="gesture_middle_relative_hint">Lo spostamento con il pulsante centrale segue sempre il movimento del dito per evitare salti del cursore.</string>
|
||||
<string name="gesture_direct">Diretto</string>
|
||||
<string name="gesture_relative">Relativo</string>
|
||||
<string name="gesture_recognition">Riconoscimento dei gesti</string>
|
||||
<string name="gesture_threshold_hint">Percorri questa distanza prima che un gesto con due o tre dita diventi uno spostamento o uno zoom.</string>
|
||||
<string name="gesture_feedback">Riscontro visivo</string>
|
||||
<string name="gesture_mouse_behavior">Comportamento del pulsante del mouse</string>
|
||||
<string name="gesture_ms">%1$d ms</string>
|
||||
|
||||
@@ -581,16 +581,6 @@
|
||||
<string name="drawer_prev_page">Pagina precedente</string>
|
||||
<string name="drawer_next_page">Pagina successiva</string>
|
||||
<string name="drawer_hud">HUD prestazioni</string>
|
||||
<string name="drawer_hud_fps">Frequenza fotogrammi</string>
|
||||
<string name="drawer_hud_battery">Temperatura batteria</string>
|
||||
<string name="drawer_hud_graph">Grafico prestazioni</string>
|
||||
<string name="drawer_hud_color">Colore</string>
|
||||
<string name="drawer_hud_color_mint">Menta</string>
|
||||
<string name="drawer_hud_color_white">Bianco</string>
|
||||
<string name="drawer_hud_color_sky">Cielo</string>
|
||||
<string name="drawer_hud_color_amber">Ambra</string>
|
||||
<string name="drawer_hud_color_violet">Viola</string>
|
||||
<string name="drawer_hud_opacity">Opacità</string>
|
||||
<string name="drawer_scaling">Modalità di ridimensionamento</string>
|
||||
<string name="drawer_scaling_sharpness">Nitidezza del ridimensionamento</string>
|
||||
<string name="drawer_effects">Effetti schermo</string>
|
||||
@@ -2046,9 +2036,6 @@
|
||||
<string name="cloud_last_sync">Ultima sincronizzazione %1$s</string>
|
||||
<string name="cloud_never">Non ancora sincronizzato</string>
|
||||
<string name="cloud_done">%1$d file</string>
|
||||
<string name="cloud_up_to_date">Già aggiornato</string>
|
||||
<string name="cloud_signed_out">Accedi allo store per sincronizzare i salvataggi</string>
|
||||
<string name="cloud_not_synced">I salvataggi non sono stati sincronizzati</string>
|
||||
<string name="stores_eyebrow_installed">installato</string>
|
||||
<string name="stores_eyebrow_library">nella tua libreria</string>
|
||||
<string name="stores_eyebrow_store">negozio</string>
|
||||
@@ -2351,7 +2338,7 @@
|
||||
<string name="touch_done">Fine</string>
|
||||
<string name="touch_save_failed">Impossibile salvare il layout touch</string>
|
||||
<string name="touch_saved">Layout touch salvato su Steam</string>
|
||||
<string name="touch_app_id">App %1$s</string>
|
||||
<string name="touch_app_id">App %1$d</string>
|
||||
<string name="touch_label_hint">Etichetta (opzionale)</string>
|
||||
<string name="touch_icons_missing">Le icone di Steam non sono state trovate</string>
|
||||
<plurals name="touch_icons_found">
|
||||
@@ -2444,15 +2431,6 @@
|
||||
<string name="glasses_vr_fov">Campo visivo di SteamVR</string>
|
||||
<string name="glasses_vr_fov_hint">Più basso avvicina SteamVR e riempie gli occhiali; più alto mostra di più intorno a te.</string>
|
||||
<string name="glasses_playing">Gioco in corso su %1$s</string>
|
||||
<string name="glasses_phone_recenter">Ricentra</string>
|
||||
<string name="glasses_phone_steamvr_menu">Menu di SteamVR</string>
|
||||
<string name="glasses_phone_switch_sticks">Levette: mani ⇄ gioco</string>
|
||||
<string name="glasses_phone_controllers">Controller: %1$s</string>
|
||||
<string name="glasses_phone_keyboard">Tastiera</string>
|
||||
<string name="glasses_phone_session_menu">Menu della sessione</string>
|
||||
<string name="glasses_phone_stop_steamvr">Arresta SteamVR</string>
|
||||
<string name="glasses_phone_brightness">Luminosità %1$d / %2$d</string>
|
||||
<string name="glasses_phone_battery">Batteria %1$d%% · %2$d °C</string>
|
||||
<string name="glasses_calibration">Calibrazione del tracciamento</string>
|
||||
<string name="glasses_calibration_hint">Riduce la deriva e allinea il riferimento orizzontale.</string>
|
||||
<string name="glasses_calibration_saved_hint">La calibrazione salvata è applicata a questi occhiali.</string>
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
<string name="gesture_click_once">1回クリック</string>
|
||||
<string name="gesture_hold_delay">長押しの待ち時間</string>
|
||||
<string name="gesture_double_delay">ダブルタップの間隔</string>
|
||||
<string name="gesture_scope_game">現在のゲームに適用</string>
|
||||
<string name="gesture_scope_default">すべてのゲームの既定設定</string>
|
||||
<string name="gesture_reset">ジェスチャーの設定を初期状態に戻す</string>
|
||||
<string name="gesture_cancel">キャンセル</string>
|
||||
<string name="gesture_action">操作を選択</string>
|
||||
@@ -35,16 +37,23 @@
|
||||
<string name="gesture_scroll">マウスホイール</string>
|
||||
<string name="gesture_threshold">ジェスチャーのしきい値:%1$d px</string>
|
||||
<string name="gesture_debug">認識したジェスチャーを表示</string>
|
||||
<string name="gesture_editor_subtitle">ジェスチャー別のマウスとキーボード操作</string>
|
||||
<string name="gesture_tab_one">1本指</string>
|
||||
<string name="gesture_tab_two">2本指</string>
|
||||
<string name="gesture_tab_three">3本指</string>
|
||||
<string name="gesture_tab_options">オプション</string>
|
||||
<string name="gesture_one_hint">タップ、ドラッグ、長押しの動作を選択します。</string>
|
||||
<string name="gesture_two_hint">カメラ移動、ズーム、補助クリックを設定します。</string>
|
||||
<string name="gesture_three_hint">追加のショートカットを手元に配置します。</string>
|
||||
<string name="gesture_options_hint">移動、認識、視覚的フィードバックを調整します。</string>
|
||||
<string name="gesture_defaults">デフォルト</string>
|
||||
<string name="gesture_game">このゲーム</string>
|
||||
<string name="gesture_use_defaults">既定値を使用</string>
|
||||
<string name="gesture_inheriting">既定設定を使用しています。編集するとゲーム固有の設定が作成されます。</string>
|
||||
<string name="gesture_save_changes">変更を保存</string>
|
||||
<string name="gesture_close">閉じる</string>
|
||||
<string name="gesture_action_label">操作</string>
|
||||
<string name="gesture_action_for">%1$sの操作</string>
|
||||
<string name="gesture_search_actions">マウス操作やキーボードのキーを検索</string>
|
||||
<string name="gesture_clear_search">検索をクリア</string>
|
||||
<string name="gesture_all_actions">すべて</string>
|
||||
@@ -59,9 +68,12 @@
|
||||
<string name="gesture_group_numpad">テンキー</string>
|
||||
<string name="gesture_group_symbols">記号</string>
|
||||
<string name="gesture_movement">マウスの移動</string>
|
||||
<string name="gesture_movement_hint">直接ドラッグはタッチ位置に従います。相対ドラッグは指の動きに従い、ゲームがマウスを中央に戻す場合に適しています。</string>
|
||||
<string name="gesture_middle_relative_hint">カーソルの飛びを防ぐため、中ボタンによる視点移動は常に指の動きに従います。</string>
|
||||
<string name="gesture_direct">ダイレクト</string>
|
||||
<string name="gesture_relative">相対</string>
|
||||
<string name="gesture_recognition">ジェスチャー認識</string>
|
||||
<string name="gesture_threshold_hint">2本指または3本指のジェスチャーが視点移動やズームになるまでに必要な移動距離です。</string>
|
||||
<string name="gesture_feedback">視覚的フィードバック</string>
|
||||
<string name="gesture_mouse_behavior">マウスボタンの動作</string>
|
||||
<string name="gesture_ms">%1$dミリ秒</string>
|
||||
|
||||
@@ -580,16 +580,6 @@
|
||||
<string name="drawer_prev_page">前のページ</string>
|
||||
<string name="drawer_next_page">次のページ</string>
|
||||
<string name="drawer_hud">パフォーマンスHUD</string>
|
||||
<string name="drawer_hud_fps">フレームレート</string>
|
||||
<string name="drawer_hud_battery">バッテリー温度</string>
|
||||
<string name="drawer_hud_graph">パフォーマンスグラフ</string>
|
||||
<string name="drawer_hud_color">色</string>
|
||||
<string name="drawer_hud_color_mint">ミント</string>
|
||||
<string name="drawer_hud_color_white">ホワイト</string>
|
||||
<string name="drawer_hud_color_sky">スカイ</string>
|
||||
<string name="drawer_hud_color_amber">アンバー</string>
|
||||
<string name="drawer_hud_color_violet">バイオレット</string>
|
||||
<string name="drawer_hud_opacity">不透明度</string>
|
||||
<string name="drawer_scaling">スケーリングモード</string>
|
||||
<string name="drawer_scaling_sharpness">スケーリングのシャープネス</string>
|
||||
<string name="drawer_effects">画面エフェクト</string>
|
||||
@@ -2015,9 +2005,6 @@
|
||||
<string name="cloud_last_sync">最終同期 %1$s</string>
|
||||
<string name="cloud_never">未同期</string>
|
||||
<string name="cloud_done">%1$d個のファイル</string>
|
||||
<string name="cloud_up_to_date">すでに最新です</string>
|
||||
<string name="cloud_signed_out">セーブを同期するにはストアにサインインしてください</string>
|
||||
<string name="cloud_not_synced">セーブは同期されませんでした</string>
|
||||
<string name="stores_eyebrow_installed">インストール済み</string>
|
||||
<string name="stores_eyebrow_library">ライブラリ内</string>
|
||||
<string name="stores_eyebrow_store">ストア</string>
|
||||
@@ -2319,7 +2306,7 @@
|
||||
<string name="touch_done">完了</string>
|
||||
<string name="touch_save_failed">タッチレイアウトを保存できませんでした</string>
|
||||
<string name="touch_saved">タッチレイアウトをSteamに保存しました</string>
|
||||
<string name="touch_app_id">アプリ%1$s</string>
|
||||
<string name="touch_app_id">アプリ%1$d</string>
|
||||
<string name="touch_label_hint">ラベル(任意)</string>
|
||||
<string name="touch_icons_missing">Steamのアイコンが見つかりませんでした</string>
|
||||
<plurals name="touch_icons_found">
|
||||
@@ -2408,15 +2395,6 @@
|
||||
<string name="glasses_vr_fov">SteamVR の視野角</string>
|
||||
<string name="glasses_vr_fov_hint">小さくすると SteamVR が近くなりグラスいっぱいに広がり、大きくすると周囲が広く見えます。</string>
|
||||
<string name="glasses_playing">%1$s でプレイ中</string>
|
||||
<string name="glasses_phone_recenter">中心をリセット</string>
|
||||
<string name="glasses_phone_steamvr_menu">SteamVR メニュー</string>
|
||||
<string name="glasses_phone_switch_sticks">スティック: 手 ⇄ ゲーム</string>
|
||||
<string name="glasses_phone_controllers">コントローラー: %1$s</string>
|
||||
<string name="glasses_phone_keyboard">キーボード</string>
|
||||
<string name="glasses_phone_session_menu">セッションメニュー</string>
|
||||
<string name="glasses_phone_stop_steamvr">SteamVR を停止</string>
|
||||
<string name="glasses_phone_brightness">明るさ %1$d / %2$d</string>
|
||||
<string name="glasses_phone_battery">バッテリー %1$d%% · %2$d °C</string>
|
||||
<string name="glasses_calibration">トラッキングのキャリブレーション</string>
|
||||
<string name="glasses_calibration_hint">ドリフトを減らし、水平の基準を合わせます。</string>
|
||||
<string name="glasses_calibration_saved_hint">保存したキャリブレーションをこのグラスに適用しています。</string>
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
<string name="gesture_click_once">한 번 클릭</string>
|
||||
<string name="gesture_hold_delay">길게 누르기 대기 시간</string>
|
||||
<string name="gesture_double_delay">두 번 탭 간격</string>
|
||||
<string name="gesture_scope_game">현재 게임에 적용</string>
|
||||
<string name="gesture_scope_default">모든 게임의 기본 설정</string>
|
||||
<string name="gesture_reset">제스처 설정을 초기 상태로 재설정</string>
|
||||
<string name="gesture_cancel">취소</string>
|
||||
<string name="gesture_action">동작 선택</string>
|
||||
@@ -35,16 +37,23 @@
|
||||
<string name="gesture_scroll">마우스 휠</string>
|
||||
<string name="gesture_threshold">제스처 임계값: %1$d px</string>
|
||||
<string name="gesture_debug">인식된 제스처 표시</string>
|
||||
<string name="gesture_editor_subtitle">제스처별 마우스 및 키보드 조작</string>
|
||||
<string name="gesture_tab_one">한 손가락</string>
|
||||
<string name="gesture_tab_two">두 손가락</string>
|
||||
<string name="gesture_tab_three">세 손가락</string>
|
||||
<string name="gesture_tab_options">옵션</string>
|
||||
<string name="gesture_one_hint">탭, 드래그, 길게 누르기의 동작을 선택하세요.</string>
|
||||
<string name="gesture_two_hint">카메라 이동, 확대/축소, 보조 클릭을 설정하세요.</string>
|
||||
<string name="gesture_three_hint">추가 단축 동작을 손쉽게 사용하세요.</string>
|
||||
<string name="gesture_options_hint">이동, 인식, 시각적 피드백을 조정하세요.</string>
|
||||
<string name="gesture_defaults">기본값</string>
|
||||
<string name="gesture_game">이 게임</string>
|
||||
<string name="gesture_use_defaults">기본값 사용</string>
|
||||
<string name="gesture_inheriting">기본 설정을 사용 중입니다. 수정하면 게임별 설정이 생성됩니다.</string>
|
||||
<string name="gesture_save_changes">변경 사항 저장</string>
|
||||
<string name="gesture_close">닫기</string>
|
||||
<string name="gesture_action_label">동작</string>
|
||||
<string name="gesture_action_for">%1$s 동작</string>
|
||||
<string name="gesture_search_actions">마우스 동작 또는 키보드 키 검색</string>
|
||||
<string name="gesture_clear_search">검색 지우기</string>
|
||||
<string name="gesture_all_actions">전체</string>
|
||||
@@ -59,9 +68,12 @@
|
||||
<string name="gesture_group_numpad">숫자 키패드</string>
|
||||
<string name="gesture_group_symbols">기호</string>
|
||||
<string name="gesture_movement">마우스 이동</string>
|
||||
<string name="gesture_movement_hint">직접 드래그는 터치 위치를 따릅니다. 상대 드래그는 손가락 움직임을 따르며, 게임에서 마우스를 중앙으로 돌려놓을 때 유용합니다.</string>
|
||||
<string name="gesture_middle_relative_hint">커서가 튀지 않도록 가운데 버튼 이동은 항상 손가락 움직임을 따릅니다.</string>
|
||||
<string name="gesture_direct">직접 터치</string>
|
||||
<string name="gesture_relative">상대</string>
|
||||
<string name="gesture_recognition">제스처 인식</string>
|
||||
<string name="gesture_threshold_hint">두 손가락 또는 세 손가락 제스처가 카메라 이동이나 확대/축소로 인식되기 위한 이동 거리입니다.</string>
|
||||
<string name="gesture_feedback">시각적 피드백</string>
|
||||
<string name="gesture_mouse_behavior">마우스 버튼 동작</string>
|
||||
<string name="gesture_ms">%1$d ms</string>
|
||||
|
||||
@@ -580,16 +580,6 @@
|
||||
<string name="drawer_prev_page">이전 페이지</string>
|
||||
<string name="drawer_next_page">다음 페이지</string>
|
||||
<string name="drawer_hud">성능 HUD</string>
|
||||
<string name="drawer_hud_fps">프레임 속도</string>
|
||||
<string name="drawer_hud_battery">배터리 온도</string>
|
||||
<string name="drawer_hud_graph">성능 그래프</string>
|
||||
<string name="drawer_hud_color">색상</string>
|
||||
<string name="drawer_hud_color_mint">민트</string>
|
||||
<string name="drawer_hud_color_white">흰색</string>
|
||||
<string name="drawer_hud_color_sky">하늘색</string>
|
||||
<string name="drawer_hud_color_amber">호박색</string>
|
||||
<string name="drawer_hud_color_violet">보라색</string>
|
||||
<string name="drawer_hud_opacity">불투명도</string>
|
||||
<string name="drawer_scaling">스케일링 모드</string>
|
||||
<string name="drawer_scaling_sharpness">스케일링 선명도</string>
|
||||
<string name="drawer_effects">화면 효과</string>
|
||||
@@ -2015,9 +2005,6 @@
|
||||
<string name="cloud_last_sync">마지막 동기화 %1$s</string>
|
||||
<string name="cloud_never">아직 동기화 안 됨</string>
|
||||
<string name="cloud_done">파일 %1$d개</string>
|
||||
<string name="cloud_up_to_date">이미 최신 상태입니다</string>
|
||||
<string name="cloud_signed_out">저장 파일을 동기화하려면 스토어에 로그인하세요</string>
|
||||
<string name="cloud_not_synced">저장 파일이 동기화되지 않았습니다</string>
|
||||
<string name="stores_eyebrow_installed">설치됨</string>
|
||||
<string name="stores_eyebrow_library">내 라이브러리</string>
|
||||
<string name="stores_eyebrow_store">스토어</string>
|
||||
@@ -2319,7 +2306,7 @@
|
||||
<string name="touch_done">완료</string>
|
||||
<string name="touch_save_failed">터치 레이아웃을 저장할 수 없습니다</string>
|
||||
<string name="touch_saved">터치 레이아웃이 Steam에 저장되었습니다</string>
|
||||
<string name="touch_app_id">앱 %1$s</string>
|
||||
<string name="touch_app_id">앱 %1$d</string>
|
||||
<string name="touch_label_hint">라벨(선택 사항)</string>
|
||||
<string name="touch_icons_missing">Steam 아이콘을 찾을 수 없습니다</string>
|
||||
<plurals name="touch_icons_found">
|
||||
@@ -2408,15 +2395,6 @@
|
||||
<string name="glasses_vr_fov">SteamVR 시야각</string>
|
||||
<string name="glasses_vr_fov_hint">낮추면 SteamVR이 가까워져 안경을 가득 채우고, 높이면 주변이 더 넓게 보입니다.</string>
|
||||
<string name="glasses_playing">%1$s에서 플레이 중</string>
|
||||
<string name="glasses_phone_recenter">중앙 재설정</string>
|
||||
<string name="glasses_phone_steamvr_menu">SteamVR 메뉴</string>
|
||||
<string name="glasses_phone_switch_sticks">스틱: 손 ⇄ 게임</string>
|
||||
<string name="glasses_phone_controllers">컨트롤러: %1$s</string>
|
||||
<string name="glasses_phone_keyboard">키보드</string>
|
||||
<string name="glasses_phone_session_menu">세션 메뉴</string>
|
||||
<string name="glasses_phone_stop_steamvr">SteamVR 중지</string>
|
||||
<string name="glasses_phone_brightness">밝기 %1$d / %2$d</string>
|
||||
<string name="glasses_phone_battery">배터리 %1$d%% · %2$d °C</string>
|
||||
<string name="glasses_calibration">트래킹 보정</string>
|
||||
<string name="glasses_calibration_hint">드리프트를 줄이고 수평 기준을 맞춥니다.</string>
|
||||
<string name="glasses_calibration_saved_hint">저장된 보정이 이 안경에 적용되어 있습니다.</string>
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
<string name="gesture_click_once">Klik sekali</string>
|
||||
<string name="gesture_hold_delay">Lengah sebelum menahan</string>
|
||||
<string name="gesture_double_delay">Sela ketikan dua kali</string>
|
||||
<string name="gesture_scope_game">Digunakan untuk permainan semasa</string>
|
||||
<string name="gesture_scope_default">Tetapan lalai untuk semua permainan</string>
|
||||
<string name="gesture_reset">Pulihkan tetapan gerak isyarat asal</string>
|
||||
<string name="gesture_cancel">Batal</string>
|
||||
<string name="gesture_action">Pilih tindakan</string>
|
||||
@@ -35,16 +37,23 @@
|
||||
<string name="gesture_scroll">Roda tetikus</string>
|
||||
<string name="gesture_threshold">Ambang gerak isyarat: %1$d px</string>
|
||||
<string name="gesture_debug">Tunjukkan gerak isyarat yang dikenal pasti</string>
|
||||
<string name="gesture_editor_subtitle">Kawalan tetikus dan papan kekunci mengikut gerak isyarat</string>
|
||||
<string name="gesture_tab_one">Satu jari</string>
|
||||
<string name="gesture_tab_two">Dua jari</string>
|
||||
<string name="gesture_tab_three">Tiga jari</string>
|
||||
<string name="gesture_tab_options">Pilihan</string>
|
||||
<string name="gesture_one_hint">Pilih tindakan apabila mengetik, menyeret atau menahan.</string>
|
||||
<string name="gesture_two_hint">Tetapkan sorotan kamera, zum dan klik sekunder.</string>
|
||||
<string name="gesture_three_hint">Pastikan pintasan tambahan mudah dicapai.</string>
|
||||
<string name="gesture_options_hint">Laraskan pergerakan, pengecaman dan maklum balas visual.</string>
|
||||
<string name="gesture_defaults">Lalai</string>
|
||||
<string name="gesture_game">Permainan ini</string>
|
||||
<string name="gesture_use_defaults">Gunakan lalai</string>
|
||||
<string name="gesture_inheriting">Menggunakan tetapan lalai. Penyuntingan mencipta tetapan khusus permainan.</string>
|
||||
<string name="gesture_save_changes">Simpan perubahan</string>
|
||||
<string name="gesture_close">Tutup</string>
|
||||
<string name="gesture_action_label">TINDAKAN</string>
|
||||
<string name="gesture_action_for">Untuk %1$s</string>
|
||||
<string name="gesture_search_actions">Cari tindakan tetikus atau kekunci papan kekunci</string>
|
||||
<string name="gesture_clear_search">Kosongkan carian</string>
|
||||
<string name="gesture_all_actions">Semua</string>
|
||||
@@ -59,9 +68,12 @@
|
||||
<string name="gesture_group_numpad">Pad nombor</string>
|
||||
<string name="gesture_group_symbols">Simbol</string>
|
||||
<string name="gesture_movement">Pergerakan tetikus</string>
|
||||
<string name="gesture_movement_hint">Seretan langsung mengikut kedudukan sentuhan. Seretan relatif mengikut pergerakan jari dan berguna apabila permainan memusatkan tetikus.</string>
|
||||
<string name="gesture_middle_relative_hint">Sorotan dengan butang tengah tetikus sentiasa mengikut pergerakan jari untuk mengelakkan lompatan kursor.</string>
|
||||
<string name="gesture_direct">Terus</string>
|
||||
<string name="gesture_relative">Relatif</string>
|
||||
<string name="gesture_recognition">Pengecaman gerak isyarat</string>
|
||||
<string name="gesture_threshold_hint">Gerakkan sejauh ini sebelum gerak isyarat dua atau tiga jari menjadi sorotan atau zum.</string>
|
||||
<string name="gesture_feedback">Maklum balas visual</string>
|
||||
<string name="gesture_mouse_behavior">Kelakuan butang tetikus</string>
|
||||
<string name="gesture_ms">%1$d ms</string>
|
||||
|
||||
@@ -580,16 +580,6 @@
|
||||
<string name="drawer_prev_page">Halaman sebelumnya</string>
|
||||
<string name="drawer_next_page">Halaman seterusnya</string>
|
||||
<string name="drawer_hud">HUD prestasi</string>
|
||||
<string name="drawer_hud_fps">Kadar bingkai</string>
|
||||
<string name="drawer_hud_battery">Suhu bateri</string>
|
||||
<string name="drawer_hud_graph">Graf prestasi</string>
|
||||
<string name="drawer_hud_color">Warna</string>
|
||||
<string name="drawer_hud_color_mint">Pudina</string>
|
||||
<string name="drawer_hud_color_white">Putih</string>
|
||||
<string name="drawer_hud_color_sky">Langit</string>
|
||||
<string name="drawer_hud_color_amber">Ambar</string>
|
||||
<string name="drawer_hud_color_violet">Ungu</string>
|
||||
<string name="drawer_hud_opacity">Kelegapan</string>
|
||||
<string name="drawer_scaling">Mod penskalaan</string>
|
||||
<string name="drawer_scaling_sharpness">Ketajaman penskalaan</string>
|
||||
<string name="drawer_effects">Kesan skrin</string>
|
||||
@@ -2015,9 +2005,6 @@
|
||||
<string name="cloud_last_sync">Segerak terakhir %1$s</string>
|
||||
<string name="cloud_never">Belum disegerakkan</string>
|
||||
<string name="cloud_done">%1$d fail</string>
|
||||
<string name="cloud_up_to_date">Sudah terkini</string>
|
||||
<string name="cloud_signed_out">Log masuk ke kedai untuk menyegerakkan simpanan</string>
|
||||
<string name="cloud_not_synced">Simpanan tidak disegerakkan</string>
|
||||
<string name="stores_eyebrow_installed">dipasang</string>
|
||||
<string name="stores_eyebrow_library">dalam pustaka anda</string>
|
||||
<string name="stores_eyebrow_store">kedai</string>
|
||||
@@ -2319,7 +2306,7 @@
|
||||
<string name="touch_done">Selesai</string>
|
||||
<string name="touch_save_failed">Susun atur sentuh tidak dapat disimpan</string>
|
||||
<string name="touch_saved">Susun atur sentuh disimpan ke Steam</string>
|
||||
<string name="touch_app_id">Apl %1$s</string>
|
||||
<string name="touch_app_id">Apl %1$d</string>
|
||||
<string name="touch_label_hint">Label (pilihan)</string>
|
||||
<string name="touch_icons_missing">Ikon Steam tidak dijumpai</string>
|
||||
<plurals name="touch_icons_found">
|
||||
@@ -2408,15 +2395,6 @@
|
||||
<string name="glasses_vr_fov">Medan pandangan SteamVR</string>
|
||||
<string name="glasses_vr_fov_hint">Lebih rendah mendekatkan SteamVR dan memenuhi cermin mata; lebih tinggi menunjukkan lebih banyak di sekeliling anda.</string>
|
||||
<string name="glasses_playing">Bermain pada %1$s</string>
|
||||
<string name="glasses_phone_recenter">Pusatkan semula</string>
|
||||
<string name="glasses_phone_steamvr_menu">Menu SteamVR</string>
|
||||
<string name="glasses_phone_switch_sticks">Kayu bedik: tangan ⇄ permainan</string>
|
||||
<string name="glasses_phone_controllers">Pengawal: %1$s</string>
|
||||
<string name="glasses_phone_keyboard">Papan kekunci</string>
|
||||
<string name="glasses_phone_session_menu">Menu sesi</string>
|
||||
<string name="glasses_phone_stop_steamvr">Hentikan SteamVR</string>
|
||||
<string name="glasses_phone_brightness">Kecerahan %1$d / %2$d</string>
|
||||
<string name="glasses_phone_battery">Bateri %1$d%% · %2$d °C</string>
|
||||
<string name="glasses_calibration">Penentukuran penjejakan</string>
|
||||
<string name="glasses_calibration_hint">Mengurangkan hanyutan dan menjajarkan rujukan mendatar.</string>
|
||||
<string name="glasses_calibration_saved_hint">Penentukuran yang disimpan digunakan pada cermin mata ini.</string>
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
<string name="gesture_click_once">Klikk én gang</string>
|
||||
<string name="gesture_hold_delay">Forsinkelse før hold</string>
|
||||
<string name="gesture_double_delay">Intervall for dobbelttrykk</string>
|
||||
<string name="gesture_scope_game">Gjelder det gjeldende spillet</string>
|
||||
<string name="gesture_scope_default">Standardinnstillinger for alle spill</string>
|
||||
<string name="gesture_reset">Gjenopprett de opprinnelige bevegelsesinnstillingene</string>
|
||||
<string name="gesture_cancel">Avbryt</string>
|
||||
<string name="gesture_action">Velg handling</string>
|
||||
@@ -35,16 +37,23 @@
|
||||
<string name="gesture_scroll">Musehjul</string>
|
||||
<string name="gesture_threshold">Bevegelsesterskel: %1$d px</string>
|
||||
<string name="gesture_debug">Vis gjenkjent bevegelse</string>
|
||||
<string name="gesture_editor_subtitle">Muse- og tastaturstyring organisert etter bevegelse</string>
|
||||
<string name="gesture_tab_one">Én finger</string>
|
||||
<string name="gesture_tab_two">To fingre</string>
|
||||
<string name="gesture_tab_three">Tre fingre</string>
|
||||
<string name="gesture_tab_options">Alternativer</string>
|
||||
<string name="gesture_one_hint">Velg hva et trykk, drag eller hold skal gjøre.</string>
|
||||
<string name="gesture_two_hint">Still inn kamerapanorering, zoom og sekundære klikk.</string>
|
||||
<string name="gesture_three_hint">Ha flere snarveier innen rekkevidde.</string>
|
||||
<string name="gesture_options_hint">Juster bevegelse, gjenkjenning og visuell tilbakemelding.</string>
|
||||
<string name="gesture_defaults">Standardverdier</string>
|
||||
<string name="gesture_game">Dette spillet</string>
|
||||
<string name="gesture_use_defaults">Bruk standardinnstillinger</string>
|
||||
<string name="gesture_inheriting">Standardinnstillinger brukes. Redigering oppretter egne innstillinger for spillet.</string>
|
||||
<string name="gesture_save_changes">Lagre endringer</string>
|
||||
<string name="gesture_close">Lukk</string>
|
||||
<string name="gesture_action_label">HANDLING</string>
|
||||
<string name="gesture_action_for">For %1$s</string>
|
||||
<string name="gesture_search_actions">Søk etter musehandlinger eller tastaturtaster</string>
|
||||
<string name="gesture_clear_search">Tøm søket</string>
|
||||
<string name="gesture_all_actions">Alle</string>
|
||||
@@ -59,9 +68,12 @@
|
||||
<string name="gesture_group_numpad">Numerisk tastatur</string>
|
||||
<string name="gesture_group_symbols">Symboler</string>
|
||||
<string name="gesture_movement">Musebevegelse</string>
|
||||
<string name="gesture_movement_hint">Direkte draing følger berøringsposisjonen. Relativ draing følger fingerbevegelsen og er nyttig når et spill sentrerer musen.</string>
|
||||
<string name="gesture_middle_relative_hint">Panorering med midtre museknapp følger alltid fingerbevegelsen for å unngå markørhopp.</string>
|
||||
<string name="gesture_direct">Direkte</string>
|
||||
<string name="gesture_relative">Relativ</string>
|
||||
<string name="gesture_recognition">Gjenkjenning av bevegelser</string>
|
||||
<string name="gesture_threshold_hint">Beveg fingrene så langt før en bevegelse med to eller tre fingre blir til panorering eller zoom.</string>
|
||||
<string name="gesture_feedback">Visuell tilbakemelding</string>
|
||||
<string name="gesture_mouse_behavior">Museknappens funksjon</string>
|
||||
<string name="gesture_ms">%1$d ms</string>
|
||||
|
||||
@@ -582,16 +582,6 @@
|
||||
<string name="drawer_prev_page">Forrige side</string>
|
||||
<string name="drawer_next_page">Neste side</string>
|
||||
<string name="drawer_hud">Ytelses-HUD</string>
|
||||
<string name="drawer_hud_fps">Bildefrekvens</string>
|
||||
<string name="drawer_hud_battery">Batteritemperatur</string>
|
||||
<string name="drawer_hud_graph">Ytelsesgraf</string>
|
||||
<string name="drawer_hud_color">Farge</string>
|
||||
<string name="drawer_hud_color_mint">Mint</string>
|
||||
<string name="drawer_hud_color_white">Hvit</string>
|
||||
<string name="drawer_hud_color_sky">Himmel</string>
|
||||
<string name="drawer_hud_color_amber">Amber</string>
|
||||
<string name="drawer_hud_color_violet">Fiolett</string>
|
||||
<string name="drawer_hud_opacity">Opasitet</string>
|
||||
<string name="drawer_scaling">Skaleringsmodus</string>
|
||||
<string name="drawer_scaling_sharpness">Skaleringsskarphet</string>
|
||||
<string name="drawer_effects">Skjermeffekter</string>
|
||||
@@ -2036,9 +2026,6 @@
|
||||
<string name="cloud_last_sync">Siste synkronisering %1$s</string>
|
||||
<string name="cloud_never">Ikke synkronisert ennå</string>
|
||||
<string name="cloud_done">%1$d filer</string>
|
||||
<string name="cloud_up_to_date">Allerede oppdatert</string>
|
||||
<string name="cloud_signed_out">Logg inn i butikken for å synkronisere lagrede spill</string>
|
||||
<string name="cloud_not_synced">Lagrede spill ble ikke synkronisert</string>
|
||||
<string name="stores_eyebrow_installed">installert</string>
|
||||
<string name="stores_eyebrow_library">i biblioteket ditt</string>
|
||||
<string name="stores_eyebrow_store">butikk</string>
|
||||
@@ -2342,7 +2329,7 @@
|
||||
<string name="touch_done">Fullført</string>
|
||||
<string name="touch_save_failed">Kunne ikke lagre berøringsoppsettet</string>
|
||||
<string name="touch_saved">Berøringsoppsett lagret til Steam</string>
|
||||
<string name="touch_app_id">App %1$s</string>
|
||||
<string name="touch_app_id">App %1$d</string>
|
||||
<string name="touch_label_hint">Etikett (valgfritt)</string>
|
||||
<string name="touch_icons_missing">Steams ikoner ble ikke funnet</string>
|
||||
<plurals name="touch_icons_found">
|
||||
@@ -2433,15 +2420,6 @@
|
||||
<string name="glasses_vr_fov">SteamVR-synsfelt</string>
|
||||
<string name="glasses_vr_fov_hint">Lavere bringer SteamVR nærmere og fyller brillene; høyere viser mer rundt deg.</string>
|
||||
<string name="glasses_playing">Spiller på %1$s</string>
|
||||
<string name="glasses_phone_recenter">Sentrer på nytt</string>
|
||||
<string name="glasses_phone_steamvr_menu">SteamVR-meny</string>
|
||||
<string name="glasses_phone_switch_sticks">Spaker: hender ⇄ spill</string>
|
||||
<string name="glasses_phone_controllers">Kontrollere: %1$s</string>
|
||||
<string name="glasses_phone_keyboard">Tastatur</string>
|
||||
<string name="glasses_phone_session_menu">Øktmeny</string>
|
||||
<string name="glasses_phone_stop_steamvr">Stopp SteamVR</string>
|
||||
<string name="glasses_phone_brightness">Lysstyrke %1$d / %2$d</string>
|
||||
<string name="glasses_phone_battery">Batteri %1$d%% · %2$d °C</string>
|
||||
<string name="glasses_calibration">Sporingskalibrering</string>
|
||||
<string name="glasses_calibration_hint">Reduserer avdrift og retter inn horisontreferansen.</string>
|
||||
<string name="glasses_calibration_saved_hint">Lagret kalibrering brukes for disse brillene.</string>
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
<string name="gesture_click_once">Eenmaal klikken</string>
|
||||
<string name="gesture_hold_delay">Wachttijd voor vasthouden</string>
|
||||
<string name="gesture_double_delay">Interval voor dubbeltikken</string>
|
||||
<string name="gesture_scope_game">Geldt voor het huidige spel</string>
|
||||
<string name="gesture_scope_default">Standaardinstellingen voor alle spellen</string>
|
||||
<string name="gesture_reset">Oorspronkelijke gebareninstellingen herstellen</string>
|
||||
<string name="gesture_cancel">Annuleren</string>
|
||||
<string name="gesture_action">Actie kiezen</string>
|
||||
@@ -35,16 +37,23 @@
|
||||
<string name="gesture_scroll">Muiswiel</string>
|
||||
<string name="gesture_threshold">Gebaardrempel: %1$d px</string>
|
||||
<string name="gesture_debug">Herkend gebaar tonen</string>
|
||||
<string name="gesture_editor_subtitle">Muis- en toetsenbordbediening per gebaar</string>
|
||||
<string name="gesture_tab_one">Eén vinger</string>
|
||||
<string name="gesture_tab_two">Twee vingers</string>
|
||||
<string name="gesture_tab_three">Drie vingers</string>
|
||||
<string name="gesture_tab_options">Opties</string>
|
||||
<string name="gesture_one_hint">Kies wat tikken, slepen of vasthouden doet.</string>
|
||||
<string name="gesture_two_hint">Stel camerabewegingen, zoomen en secundaire klikken in.</string>
|
||||
<string name="gesture_three_hint">Houd extra sneltoetsen binnen handbereik.</string>
|
||||
<string name="gesture_options_hint">Pas beweging, herkenning en visuele feedback aan.</string>
|
||||
<string name="gesture_defaults">Standaardinstellingen</string>
|
||||
<string name="gesture_game">Dit spel</string>
|
||||
<string name="gesture_use_defaults">Standaardinstellingen gebruiken</string>
|
||||
<string name="gesture_inheriting">Standaardinstellingen zijn actief. Bewerken maakt instellingen voor dit spel.</string>
|
||||
<string name="gesture_save_changes">Wijzigingen opslaan</string>
|
||||
<string name="gesture_close">Sluiten</string>
|
||||
<string name="gesture_action_label">ACTIE</string>
|
||||
<string name="gesture_action_for">Voor %1$s</string>
|
||||
<string name="gesture_search_actions">Zoek muisacties of toetsen</string>
|
||||
<string name="gesture_clear_search">Zoekopdracht wissen</string>
|
||||
<string name="gesture_all_actions">Alle</string>
|
||||
@@ -59,9 +68,12 @@
|
||||
<string name="gesture_group_numpad">Numeriek toetsenblok</string>
|
||||
<string name="gesture_group_symbols">Symbolen</string>
|
||||
<string name="gesture_movement">Muisbeweging</string>
|
||||
<string name="gesture_movement_hint">Direct slepen volgt de aanraakpositie. Relatief slepen volgt de vingerbeweging en is handig als een spel de muis opnieuw centreert.</string>
|
||||
<string name="gesture_middle_relative_hint">Pannen met de middelste muisknop volgt altijd de vingerbeweging om cursorsprongen te voorkomen.</string>
|
||||
<string name="gesture_direct">Direct</string>
|
||||
<string name="gesture_relative">Relatief</string>
|
||||
<string name="gesture_recognition">Gebarenherkenning</string>
|
||||
<string name="gesture_threshold_hint">Beweeg deze afstand voordat een gebaar met twee of drie vingers overgaat in pannen of zoomen.</string>
|
||||
<string name="gesture_feedback">Visuele feedback</string>
|
||||
<string name="gesture_mouse_behavior">Gedrag van de muisknop</string>
|
||||
<string name="gesture_ms">%1$d ms</string>
|
||||
|
||||
@@ -582,16 +582,6 @@
|
||||
<string name="drawer_prev_page">Vorige pagina</string>
|
||||
<string name="drawer_next_page">Volgende pagina</string>
|
||||
<string name="drawer_hud">Prestatie-HUD</string>
|
||||
<string name="drawer_hud_fps">Beeldsnelheid</string>
|
||||
<string name="drawer_hud_battery">Accutemperatuur</string>
|
||||
<string name="drawer_hud_graph">Prestatiegrafiek</string>
|
||||
<string name="drawer_hud_color">Kleur</string>
|
||||
<string name="drawer_hud_color_mint">Mint</string>
|
||||
<string name="drawer_hud_color_white">Wit</string>
|
||||
<string name="drawer_hud_color_sky">Hemelsblauw</string>
|
||||
<string name="drawer_hud_color_amber">Amber</string>
|
||||
<string name="drawer_hud_color_violet">Violet</string>
|
||||
<string name="drawer_hud_opacity">Transparantie</string>
|
||||
<string name="drawer_scaling">Schaalmodus</string>
|
||||
<string name="drawer_scaling_sharpness">Schaalscherpte</string>
|
||||
<string name="drawer_effects">Schermeffecten</string>
|
||||
@@ -2036,9 +2026,6 @@
|
||||
<string name="cloud_last_sync">Laatste synchronisatie %1$s</string>
|
||||
<string name="cloud_never">Nog niet gesynchroniseerd</string>
|
||||
<string name="cloud_done">%1$d bestanden</string>
|
||||
<string name="cloud_up_to_date">Al up-to-date</string>
|
||||
<string name="cloud_signed_out">Log in bij de winkel om opslagbestanden te synchroniseren</string>
|
||||
<string name="cloud_not_synced">Opslagbestanden zijn niet gesynchroniseerd</string>
|
||||
<string name="stores_eyebrow_installed">geïnstalleerd</string>
|
||||
<string name="stores_eyebrow_library">in je bibliotheek</string>
|
||||
<string name="stores_eyebrow_store">winkel</string>
|
||||
@@ -2342,7 +2329,7 @@
|
||||
<string name="touch_done">Klaar</string>
|
||||
<string name="touch_save_failed">De aanraakindeling kon niet worden opgeslagen</string>
|
||||
<string name="touch_saved">Aanraakindeling opgeslagen in Steam</string>
|
||||
<string name="touch_app_id">App %1$s</string>
|
||||
<string name="touch_app_id">App %1$d</string>
|
||||
<string name="touch_label_hint">Label (optioneel)</string>
|
||||
<string name="touch_icons_missing">Steams pictogrammen zijn niet gevonden</string>
|
||||
<plurals name="touch_icons_found">
|
||||
@@ -2433,15 +2420,6 @@
|
||||
<string name="glasses_vr_fov">SteamVR-gezichtsveld</string>
|
||||
<string name="glasses_vr_fov_hint">Lager brengt SteamVR dichterbij en vult de bril; hoger toont meer om je heen.</string>
|
||||
<string name="glasses_playing">Je speelt op %1$s</string>
|
||||
<string name="glasses_phone_recenter">Opnieuw centreren</string>
|
||||
<string name="glasses_phone_steamvr_menu">SteamVR-menu</string>
|
||||
<string name="glasses_phone_switch_sticks">Sticks: handen ⇄ spel</string>
|
||||
<string name="glasses_phone_controllers">Controllers: %1$s</string>
|
||||
<string name="glasses_phone_keyboard">Toetsenbord</string>
|
||||
<string name="glasses_phone_session_menu">Sessiemenu</string>
|
||||
<string name="glasses_phone_stop_steamvr">SteamVR stoppen</string>
|
||||
<string name="glasses_phone_brightness">Helderheid %1$d / %2$d</string>
|
||||
<string name="glasses_phone_battery">Batterij %1$d%% · %2$d °C</string>
|
||||
<string name="glasses_calibration">Trackingkalibratie</string>
|
||||
<string name="glasses_calibration_hint">Vermindert drift en lijnt de horizontale referentie uit.</string>
|
||||
<string name="glasses_calibration_saved_hint">De opgeslagen kalibratie wordt op deze bril toegepast.</string>
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
<string name="gesture_click_once">Kliknij raz</string>
|
||||
<string name="gesture_hold_delay">Czas przytrzymania</string>
|
||||
<string name="gesture_double_delay">Odstęp podwójnego stuknięcia</string>
|
||||
<string name="gesture_scope_game">Dotyczy bieżącej gry</string>
|
||||
<string name="gesture_scope_default">Ustawienia domyślne dla wszystkich gier</string>
|
||||
<string name="gesture_reset">Przywróć pierwotne ustawienia gestów</string>
|
||||
<string name="gesture_cancel">Anuluj</string>
|
||||
<string name="gesture_action">Wybierz akcję</string>
|
||||
@@ -35,16 +37,23 @@
|
||||
<string name="gesture_scroll">Kółko myszy</string>
|
||||
<string name="gesture_threshold">Próg gestów: %1$d px</string>
|
||||
<string name="gesture_debug">Pokaż rozpoznany gest</string>
|
||||
<string name="gesture_editor_subtitle">Sterowanie myszą i klawiaturą pogrupowane według gestów</string>
|
||||
<string name="gesture_tab_one">Jeden palec</string>
|
||||
<string name="gesture_tab_two">Dwa palce</string>
|
||||
<string name="gesture_tab_three">Trzy palce</string>
|
||||
<string name="gesture_tab_options">Opcje</string>
|
||||
<string name="gesture_one_hint">Wybierz działanie stuknięcia, przeciągania lub przytrzymania.</string>
|
||||
<string name="gesture_two_hint">Skonfiguruj przesuwanie kamery, przybliżanie i kliknięcia pomocnicze.</string>
|
||||
<string name="gesture_three_hint">Miej dodatkowe skróty pod ręką.</string>
|
||||
<string name="gesture_options_hint">Dostosuj ruch, rozpoznawanie i wizualne potwierdzenia.</string>
|
||||
<string name="gesture_defaults">Domyślne</string>
|
||||
<string name="gesture_game">Ta gra</string>
|
||||
<string name="gesture_use_defaults">Użyj domyślnych</string>
|
||||
<string name="gesture_inheriting">Używane są ustawienia domyślne. Edycja tworzy ustawienia dla tej gry.</string>
|
||||
<string name="gesture_save_changes">Zapisz zmiany</string>
|
||||
<string name="gesture_close">Zamknij</string>
|
||||
<string name="gesture_action_label">AKCJA</string>
|
||||
<string name="gesture_action_for">Dla %1$s</string>
|
||||
<string name="gesture_search_actions">Szukaj akcji myszy lub klawiszy klawiatury</string>
|
||||
<string name="gesture_clear_search">Wyczyść wyszukiwanie</string>
|
||||
<string name="gesture_all_actions">Wszystkie</string>
|
||||
@@ -59,9 +68,12 @@
|
||||
<string name="gesture_group_numpad">Klawiatura numeryczna</string>
|
||||
<string name="gesture_group_symbols">Symbole</string>
|
||||
<string name="gesture_movement">Ruch myszy</string>
|
||||
<string name="gesture_movement_hint">Przeciąganie bezpośrednie podąża za pozycją dotyku. Względne podąża za ruchem palca i przydaje się, gdy gra centruje mysz.</string>
|
||||
<string name="gesture_middle_relative_hint">Przesuwanie środkowym przyciskiem myszy zawsze podąża za ruchem palca, aby uniknąć skoków kursora.</string>
|
||||
<string name="gesture_direct">Bezpośredni</string>
|
||||
<string name="gesture_relative">Względny</string>
|
||||
<string name="gesture_recognition">Rozpoznawanie gestów</string>
|
||||
<string name="gesture_threshold_hint">Przesuń palce o tę odległość, zanim gest dwoma lub trzema palcami stanie się przesuwaniem lub przybliżaniem.</string>
|
||||
<string name="gesture_feedback">Wizualne potwierdzenia</string>
|
||||
<string name="gesture_mouse_behavior">Działanie przycisku myszy</string>
|
||||
<string name="gesture_ms">%1$d ms</string>
|
||||
|
||||
@@ -586,16 +586,6 @@
|
||||
<string name="drawer_prev_page">Poprzednia strona</string>
|
||||
<string name="drawer_next_page">Następna strona</string>
|
||||
<string name="drawer_hud">HUD wydajności</string>
|
||||
<string name="drawer_hud_fps">Liczba klatek</string>
|
||||
<string name="drawer_hud_battery">Temperatura baterii</string>
|
||||
<string name="drawer_hud_graph">Wykres wydajności</string>
|
||||
<string name="drawer_hud_color">Kolor</string>
|
||||
<string name="drawer_hud_color_mint">Mięta</string>
|
||||
<string name="drawer_hud_color_white">Biały</string>
|
||||
<string name="drawer_hud_color_sky">Błękitny</string>
|
||||
<string name="drawer_hud_color_amber">Bursztynowy</string>
|
||||
<string name="drawer_hud_color_violet">Fioletowy</string>
|
||||
<string name="drawer_hud_opacity">Przezroczystość</string>
|
||||
<string name="drawer_scaling">Tryb skalowania</string>
|
||||
<string name="drawer_scaling_sharpness">Ostrość skalowania</string>
|
||||
<string name="drawer_effects">Efekty ekranowe</string>
|
||||
@@ -2078,9 +2068,6 @@
|
||||
<string name="cloud_last_sync">Ostatnia synchronizacja %1$s</string>
|
||||
<string name="cloud_never">Jeszcze nie zsynchronizowano</string>
|
||||
<string name="cloud_done">%1$d plików</string>
|
||||
<string name="cloud_up_to_date">Już aktualne</string>
|
||||
<string name="cloud_signed_out">Zaloguj się do sklepu, aby synchronizować zapisy</string>
|
||||
<string name="cloud_not_synced">Zapisy nie zostały zsynchronizowane</string>
|
||||
<string name="stores_eyebrow_installed">zainstalowana</string>
|
||||
<string name="stores_eyebrow_library">w Twojej bibliotece</string>
|
||||
<string name="stores_eyebrow_store">sklep</string>
|
||||
@@ -2388,7 +2375,7 @@
|
||||
<string name="touch_done">Gotowe</string>
|
||||
<string name="touch_save_failed">Nie udało się zapisać układu dotykowego</string>
|
||||
<string name="touch_saved">Układ dotykowy zapisany w Steam</string>
|
||||
<string name="touch_app_id">Aplikacja %1$s</string>
|
||||
<string name="touch_app_id">Aplikacja %1$d</string>
|
||||
<string name="touch_label_hint">Etykieta (opcjonalnie)</string>
|
||||
<string name="touch_icons_missing">Nie znaleziono ikon Steam</string>
|
||||
<plurals name="touch_icons_found">
|
||||
@@ -2483,15 +2470,6 @@
|
||||
<string name="glasses_vr_fov">Pole widzenia SteamVR</string>
|
||||
<string name="glasses_vr_fov_hint">Mniejsze przybliża SteamVR i wypełnia okulary; większe pokazuje więcej dookoła.</string>
|
||||
<string name="glasses_playing">Grasz na: %1$s</string>
|
||||
<string name="glasses_phone_recenter">Wyśrodkuj ponownie</string>
|
||||
<string name="glasses_phone_steamvr_menu">Menu SteamVR</string>
|
||||
<string name="glasses_phone_switch_sticks">Gałki: dłonie ⇄ gra</string>
|
||||
<string name="glasses_phone_controllers">Kontrolery: %1$s</string>
|
||||
<string name="glasses_phone_keyboard">Klawiatura</string>
|
||||
<string name="glasses_phone_session_menu">Menu sesji</string>
|
||||
<string name="glasses_phone_stop_steamvr">Zatrzymaj SteamVR</string>
|
||||
<string name="glasses_phone_brightness">Jasność %1$d / %2$d</string>
|
||||
<string name="glasses_phone_battery">Bateria %1$d%% · %2$d °C</string>
|
||||
<string name="glasses_calibration">Kalibracja śledzenia</string>
|
||||
<string name="glasses_calibration_hint">Ogranicza dryf i wyrównuje poziom odniesienia.</string>
|
||||
<string name="glasses_calibration_saved_hint">Dla tych okularów używana jest zapisana kalibracja.</string>
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
<string name="gesture_click_once">Clicar uma vez</string>
|
||||
<string name="gesture_hold_delay">Tempo para manter premido</string>
|
||||
<string name="gesture_double_delay">Intervalo de toque duplo</string>
|
||||
<string name="gesture_scope_game">Aplica-se ao jogo atual</string>
|
||||
<string name="gesture_scope_default">Configurações padrão para todos os jogos</string>
|
||||
<string name="gesture_reset">Restaurar as configurações originais de gestos</string>
|
||||
<string name="gesture_cancel">Cancelar</string>
|
||||
<string name="gesture_action">Escolher ação</string>
|
||||
@@ -35,16 +37,23 @@
|
||||
<string name="gesture_scroll">Roda do rato</string>
|
||||
<string name="gesture_threshold">Limiar dos gestos: %1$d px</string>
|
||||
<string name="gesture_debug">Mostrar gesto reconhecido</string>
|
||||
<string name="gesture_editor_subtitle">Controlos de rato e teclado organizados por gesto</string>
|
||||
<string name="gesture_tab_one">Um dedo</string>
|
||||
<string name="gesture_tab_two">Dois dedos</string>
|
||||
<string name="gesture_tab_three">Três dedos</string>
|
||||
<string name="gesture_tab_options">Opções</string>
|
||||
<string name="gesture_one_hint">Escolha o que um toque, arraste ou toque mantido deve fazer.</string>
|
||||
<string name="gesture_two_hint">Configure o movimento da câmera, o zoom e os cliques secundários.</string>
|
||||
<string name="gesture_three_hint">Mantenha mais atalhos ao alcance dos dedos.</string>
|
||||
<string name="gesture_options_hint">Ajuste o movimento, o reconhecimento e a resposta visual.</string>
|
||||
<string name="gesture_defaults">Valores padrão</string>
|
||||
<string name="gesture_game">Este jogo</string>
|
||||
<string name="gesture_use_defaults">Usar padrões</string>
|
||||
<string name="gesture_inheriting">Usando as configurações padrão. A edição cria configurações específicas do jogo.</string>
|
||||
<string name="gesture_save_changes">Salvar alterações</string>
|
||||
<string name="gesture_close">Fechar</string>
|
||||
<string name="gesture_action_label">AÇÃO</string>
|
||||
<string name="gesture_action_for">Para %1$s</string>
|
||||
<string name="gesture_search_actions">Pesquisar ações de rato ou teclas do teclado</string>
|
||||
<string name="gesture_clear_search">Limpar pesquisa</string>
|
||||
<string name="gesture_all_actions">Todas</string>
|
||||
@@ -59,9 +68,12 @@
|
||||
<string name="gesture_group_numpad">Teclado numérico</string>
|
||||
<string name="gesture_group_symbols">Símbolos</string>
|
||||
<string name="gesture_movement">Movimento do rato</string>
|
||||
<string name="gesture_movement_hint">O arraste direto segue a posição do toque. O relativo segue o movimento do dedo e é útil quando um jogo centra o rato.</string>
|
||||
<string name="gesture_middle_relative_hint">O movimento com o botão do meio sempre segue o movimento do dedo para evitar saltos do cursor.</string>
|
||||
<string name="gesture_direct">Direto</string>
|
||||
<string name="gesture_relative">Relativo</string>
|
||||
<string name="gesture_recognition">Reconhecimento de gestos</string>
|
||||
<string name="gesture_threshold_hint">Percorra esta distância antes que um gesto de dois ou três dedos se torne movimento de câmera ou zoom.</string>
|
||||
<string name="gesture_feedback">Resposta visual</string>
|
||||
<string name="gesture_mouse_behavior">Comportamento do botão do rato</string>
|
||||
<string name="gesture_ms">%1$d ms</string>
|
||||
|
||||
@@ -581,16 +581,6 @@
|
||||
<string name="drawer_prev_page">Página anterior</string>
|
||||
<string name="drawer_next_page">Página seguinte</string>
|
||||
<string name="drawer_hud">HUD de desempenho</string>
|
||||
<string name="drawer_hud_fps">Taxa de fotogramas</string>
|
||||
<string name="drawer_hud_battery">Temperatura da bateria</string>
|
||||
<string name="drawer_hud_graph">Gráfico de desempenho</string>
|
||||
<string name="drawer_hud_color">Cor</string>
|
||||
<string name="drawer_hud_color_mint">Menta</string>
|
||||
<string name="drawer_hud_color_white">Branco</string>
|
||||
<string name="drawer_hud_color_sky">Céu</string>
|
||||
<string name="drawer_hud_color_amber">Âmbar</string>
|
||||
<string name="drawer_hud_color_violet">Violeta</string>
|
||||
<string name="drawer_hud_opacity">Opacidade</string>
|
||||
<string name="drawer_scaling">Modo de escala</string>
|
||||
<string name="drawer_scaling_sharpness">Nitidez da escala</string>
|
||||
<string name="drawer_effects">Efeitos de ecrã</string>
|
||||
@@ -2046,9 +2036,6 @@
|
||||
<string name="cloud_last_sync">Última sincronização: %1$s</string>
|
||||
<string name="cloud_never">Ainda não sincronizado</string>
|
||||
<string name="cloud_done">%1$d ficheiros</string>
|
||||
<string name="cloud_up_to_date">Já está atualizado</string>
|
||||
<string name="cloud_signed_out">Inicie sessão na loja para sincronizar os jogos guardados</string>
|
||||
<string name="cloud_not_synced">Os jogos guardados não foram sincronizados</string>
|
||||
<string name="stores_eyebrow_installed">instalado</string>
|
||||
<string name="stores_eyebrow_library">na sua biblioteca</string>
|
||||
<string name="stores_eyebrow_store">loja</string>
|
||||
@@ -2351,7 +2338,7 @@
|
||||
<string name="touch_done">Concluído</string>
|
||||
<string name="touch_save_failed">Não foi possível guardar o esquema tátil</string>
|
||||
<string name="touch_saved">Esquema tátil guardado na Steam</string>
|
||||
<string name="touch_app_id">Aplicação %1$s</string>
|
||||
<string name="touch_app_id">Aplicação %1$d</string>
|
||||
<string name="touch_label_hint">Etiqueta (opcional)</string>
|
||||
<string name="touch_icons_missing">Os ícones da Steam não foram encontrados</string>
|
||||
<plurals name="touch_icons_found">
|
||||
@@ -2444,15 +2431,6 @@
|
||||
<string name="glasses_vr_fov">Campo de visão do SteamVR</string>
|
||||
<string name="glasses_vr_fov_hint">Mais baixo aproxima o SteamVR e preenche os óculos; mais alto mostra mais à sua volta.</string>
|
||||
<string name="glasses_playing">A jogar em %1$s</string>
|
||||
<string name="glasses_phone_recenter">Recentrar</string>
|
||||
<string name="glasses_phone_steamvr_menu">Menu do SteamVR</string>
|
||||
<string name="glasses_phone_switch_sticks">Manípulos: mãos ⇄ jogo</string>
|
||||
<string name="glasses_phone_controllers">Comandos: %1$s</string>
|
||||
<string name="glasses_phone_keyboard">Teclado</string>
|
||||
<string name="glasses_phone_session_menu">Menu da sessão</string>
|
||||
<string name="glasses_phone_stop_steamvr">Parar o SteamVR</string>
|
||||
<string name="glasses_phone_brightness">Brilho %1$d / %2$d</string>
|
||||
<string name="glasses_phone_battery">Bateria %1$d%% · %2$d °C</string>
|
||||
<string name="glasses_calibration">Calibração do seguimento</string>
|
||||
<string name="glasses_calibration_hint">Reduz a deriva e alinha a referência de nível.</string>
|
||||
<string name="glasses_calibration_saved_hint">A calibração guardada é aplicada a estes óculos.</string>
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
<string name="gesture_click_once">Clicar uma vez</string>
|
||||
<string name="gesture_hold_delay">Tempo para manter pressionado</string>
|
||||
<string name="gesture_double_delay">Intervalo de toque duplo</string>
|
||||
<string name="gesture_scope_game">Aplica-se ao jogo atual</string>
|
||||
<string name="gesture_scope_default">Configurações padrão para todos os jogos</string>
|
||||
<string name="gesture_reset">Restaurar as configurações originais de gestos</string>
|
||||
<string name="gesture_cancel">Cancelar</string>
|
||||
<string name="gesture_action">Escolher ação</string>
|
||||
@@ -35,16 +37,23 @@
|
||||
<string name="gesture_scroll">Roda do mouse</string>
|
||||
<string name="gesture_threshold">Limiar dos gestos: %1$d px</string>
|
||||
<string name="gesture_debug">Mostrar gesto reconhecido</string>
|
||||
<string name="gesture_editor_subtitle">Controles de mouse e teclado organizados por gesto</string>
|
||||
<string name="gesture_tab_one">Um dedo</string>
|
||||
<string name="gesture_tab_two">Dois dedos</string>
|
||||
<string name="gesture_tab_three">Três dedos</string>
|
||||
<string name="gesture_tab_options">Opções</string>
|
||||
<string name="gesture_one_hint">Escolha o que um toque, arraste ou toque mantido deve fazer.</string>
|
||||
<string name="gesture_two_hint">Configure o movimento da câmera, o zoom e os cliques secundários.</string>
|
||||
<string name="gesture_three_hint">Mantenha mais atalhos ao alcance dos dedos.</string>
|
||||
<string name="gesture_options_hint">Ajuste o movimento, o reconhecimento e a resposta visual.</string>
|
||||
<string name="gesture_defaults">Padrões</string>
|
||||
<string name="gesture_game">Este jogo</string>
|
||||
<string name="gesture_use_defaults">Usar padrões</string>
|
||||
<string name="gesture_inheriting">Usando as configurações padrão. A edição cria configurações específicas do jogo.</string>
|
||||
<string name="gesture_save_changes">Salvar alterações</string>
|
||||
<string name="gesture_close">Fechar</string>
|
||||
<string name="gesture_action_label">AÇÃO</string>
|
||||
<string name="gesture_action_for">Para %1$s</string>
|
||||
<string name="gesture_search_actions">Pesquisar ações de mouse ou teclas do teclado</string>
|
||||
<string name="gesture_clear_search">Limpar pesquisa</string>
|
||||
<string name="gesture_all_actions">Todos</string>
|
||||
@@ -59,9 +68,12 @@
|
||||
<string name="gesture_group_numpad">Teclado numérico</string>
|
||||
<string name="gesture_group_symbols">Símbolos</string>
|
||||
<string name="gesture_movement">Movimento do mouse</string>
|
||||
<string name="gesture_movement_hint">O arraste direto segue a posição do toque. O relativo segue o movimento do dedo e é útil quando um jogo centraliza o mouse.</string>
|
||||
<string name="gesture_middle_relative_hint">O movimento com o botão do meio sempre segue o movimento do dedo para evitar saltos do cursor.</string>
|
||||
<string name="gesture_direct">Direto</string>
|
||||
<string name="gesture_relative">Relativo</string>
|
||||
<string name="gesture_recognition">Reconhecimento de gestos</string>
|
||||
<string name="gesture_threshold_hint">Percorra esta distância antes que um gesto de dois ou três dedos se torne movimento de câmera ou zoom.</string>
|
||||
<string name="gesture_feedback">Resposta visual</string>
|
||||
<string name="gesture_mouse_behavior">Comportamento do botão do mouse</string>
|
||||
<string name="gesture_ms">%1$d ms</string>
|
||||
|
||||
@@ -581,16 +581,6 @@
|
||||
<string name="drawer_prev_page">Página anterior</string>
|
||||
<string name="drawer_next_page">Próxima página</string>
|
||||
<string name="drawer_hud">HUD de desempenho</string>
|
||||
<string name="drawer_hud_fps">Taxa de quadros</string>
|
||||
<string name="drawer_hud_battery">Temperatura da bateria</string>
|
||||
<string name="drawer_hud_graph">Gráfico de desempenho</string>
|
||||
<string name="drawer_hud_color">Cor</string>
|
||||
<string name="drawer_hud_color_mint">Menta</string>
|
||||
<string name="drawer_hud_color_white">Branco</string>
|
||||
<string name="drawer_hud_color_sky">Céu</string>
|
||||
<string name="drawer_hud_color_amber">Âmbar</string>
|
||||
<string name="drawer_hud_color_violet">Violeta</string>
|
||||
<string name="drawer_hud_opacity">Opacidade</string>
|
||||
<string name="drawer_scaling">Modo de escala</string>
|
||||
<string name="drawer_scaling_sharpness">Nitidez da escala</string>
|
||||
<string name="drawer_effects">Efeitos de tela</string>
|
||||
@@ -2046,9 +2036,6 @@
|
||||
<string name="cloud_last_sync">Última sincronização: %1$s</string>
|
||||
<string name="cloud_never">Ainda não sincronizado</string>
|
||||
<string name="cloud_done">%1$d arquivos</string>
|
||||
<string name="cloud_up_to_date">Já está atualizado</string>
|
||||
<string name="cloud_signed_out">Entre na loja para sincronizar os saves</string>
|
||||
<string name="cloud_not_synced">Os saves não foram sincronizados</string>
|
||||
<string name="stores_eyebrow_installed">instalado</string>
|
||||
<string name="stores_eyebrow_library">na sua biblioteca</string>
|
||||
<string name="stores_eyebrow_store">loja</string>
|
||||
@@ -2351,7 +2338,7 @@
|
||||
<string name="touch_done">Concluído</string>
|
||||
<string name="touch_save_failed">Não foi possível salvar o layout de toque</string>
|
||||
<string name="touch_saved">Layout de toque salvo na Steam</string>
|
||||
<string name="touch_app_id">App %1$s</string>
|
||||
<string name="touch_app_id">App %1$d</string>
|
||||
<string name="touch_label_hint">Rótulo (opcional)</string>
|
||||
<string name="touch_icons_missing">Os ícones da Steam não foram encontrados</string>
|
||||
<plurals name="touch_icons_found">
|
||||
@@ -2444,15 +2431,6 @@
|
||||
<string name="glasses_vr_fov">Campo de visão do SteamVR</string>
|
||||
<string name="glasses_vr_fov_hint">Menor aproxima o SteamVR e preenche os óculos; maior mostra mais ao seu redor.</string>
|
||||
<string name="glasses_playing">Jogando em %1$s</string>
|
||||
<string name="glasses_phone_recenter">Recentralizar</string>
|
||||
<string name="glasses_phone_steamvr_menu">Menu do SteamVR</string>
|
||||
<string name="glasses_phone_switch_sticks">Analógicos: mãos ⇄ jogo</string>
|
||||
<string name="glasses_phone_controllers">Controles: %1$s</string>
|
||||
<string name="glasses_phone_keyboard">Teclado</string>
|
||||
<string name="glasses_phone_session_menu">Menu da sessão</string>
|
||||
<string name="glasses_phone_stop_steamvr">Parar o SteamVR</string>
|
||||
<string name="glasses_phone_brightness">Brilho %1$d / %2$d</string>
|
||||
<string name="glasses_phone_battery">Bateria %1$d%% · %2$d °C</string>
|
||||
<string name="glasses_calibration">Calibração do rastreamento</string>
|
||||
<string name="glasses_calibration_hint">Reduz a deriva e alinha a referência de nível.</string>
|
||||
<string name="glasses_calibration_saved_hint">A calibração salva é aplicada a estes óculos.</string>
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
<string name="gesture_click_once">Un singur clic</string>
|
||||
<string name="gesture_hold_delay">Durata până la menținere</string>
|
||||
<string name="gesture_double_delay">Intervalul atingerii duble</string>
|
||||
<string name="gesture_scope_game">Se aplică jocului curent</string>
|
||||
<string name="gesture_scope_default">Setări implicite pentru toate jocurile</string>
|
||||
<string name="gesture_reset">Restabilește setările originale ale gesturilor</string>
|
||||
<string name="gesture_cancel">Anulează</string>
|
||||
<string name="gesture_action">Alege acțiunea</string>
|
||||
@@ -35,16 +37,23 @@
|
||||
<string name="gesture_scroll">Rotița mouse-ului</string>
|
||||
<string name="gesture_threshold">Pragul gesturilor: %1$d px</string>
|
||||
<string name="gesture_debug">Arată gestul recunoscut</string>
|
||||
<string name="gesture_editor_subtitle">Comenzi de mouse și tastatură organizate după gest</string>
|
||||
<string name="gesture_tab_one">Un deget</string>
|
||||
<string name="gesture_tab_two">Două degete</string>
|
||||
<string name="gesture_tab_three">Trei degete</string>
|
||||
<string name="gesture_tab_options">Opțiuni</string>
|
||||
<string name="gesture_one_hint">Alege acțiunea unei atingeri, trageri sau mențineri.</string>
|
||||
<string name="gesture_two_hint">Configurează deplasarea camerei, zoomul și clicurile secundare.</string>
|
||||
<string name="gesture_three_hint">Păstrează scurtături suplimentare la îndemână.</string>
|
||||
<string name="gesture_options_hint">Ajustează mișcarea, recunoașterea și feedbackul vizual.</string>
|
||||
<string name="gesture_defaults">Valori implicite</string>
|
||||
<string name="gesture_game">Acest joc</string>
|
||||
<string name="gesture_use_defaults">Folosește valorile implicite</string>
|
||||
<string name="gesture_inheriting">Se folosesc setările implicite. Editarea creează setări specifice jocului.</string>
|
||||
<string name="gesture_save_changes">Salvează modificările</string>
|
||||
<string name="gesture_close">Închide</string>
|
||||
<string name="gesture_action_label">ACȚIUNE</string>
|
||||
<string name="gesture_action_for">Pentru %1$s</string>
|
||||
<string name="gesture_search_actions">Caută acțiuni de mouse sau taste</string>
|
||||
<string name="gesture_clear_search">Șterge căutarea</string>
|
||||
<string name="gesture_all_actions">Toate</string>
|
||||
@@ -59,9 +68,12 @@
|
||||
<string name="gesture_group_numpad">Tastatură numerică</string>
|
||||
<string name="gesture_group_symbols">Simboluri</string>
|
||||
<string name="gesture_movement">Mișcarea mouse-ului</string>
|
||||
<string name="gesture_movement_hint">Tragerea directă urmărește poziția atingerii. Cea relativă urmărește mișcarea degetului și este utilă când jocul recentrează mouse-ul.</string>
|
||||
<string name="gesture_middle_relative_hint">Deplasarea cu butonul din mijloc urmărește întotdeauna mișcarea degetului pentru a evita salturile cursorului.</string>
|
||||
<string name="gesture_direct">Direct</string>
|
||||
<string name="gesture_relative">Relativă</string>
|
||||
<string name="gesture_recognition">Recunoașterea gesturilor</string>
|
||||
<string name="gesture_threshold_hint">Parcurge această distanță înainte ca un gest cu două sau trei degete să devină deplasare sau zoom.</string>
|
||||
<string name="gesture_feedback">Feedback vizual</string>
|
||||
<string name="gesture_mouse_behavior">Comportamentul butonului de mouse</string>
|
||||
<string name="gesture_ms">%1$d ms</string>
|
||||
|
||||
@@ -584,16 +584,6 @@
|
||||
<string name="drawer_prev_page">Pagina anterioară</string>
|
||||
<string name="drawer_next_page">Pagina următoare</string>
|
||||
<string name="drawer_hud">HUD de performanță</string>
|
||||
<string name="drawer_hud_fps">Rată de cadre</string>
|
||||
<string name="drawer_hud_battery">Temperatură baterie</string>
|
||||
<string name="drawer_hud_graph">Grafic de performanță</string>
|
||||
<string name="drawer_hud_color">Culoare</string>
|
||||
<string name="drawer_hud_color_mint">Mentă</string>
|
||||
<string name="drawer_hud_color_white">Alb</string>
|
||||
<string name="drawer_hud_color_sky">Cer</string>
|
||||
<string name="drawer_hud_color_amber">Chihlimbariu</string>
|
||||
<string name="drawer_hud_color_violet">Violet</string>
|
||||
<string name="drawer_hud_opacity">Opacitate</string>
|
||||
<string name="drawer_scaling">Mod de scalare</string>
|
||||
<string name="drawer_scaling_sharpness">Claritatea scalării</string>
|
||||
<string name="drawer_effects">Efecte de ecran</string>
|
||||
@@ -2057,9 +2047,6 @@
|
||||
<string name="cloud_last_sync">Ultima sincronizare %1$s</string>
|
||||
<string name="cloud_never">Nesincronizat încă</string>
|
||||
<string name="cloud_done">%1$d fișiere</string>
|
||||
<string name="cloud_up_to_date">Deja actualizat</string>
|
||||
<string name="cloud_signed_out">Conectează-te la magazin pentru a sincroniza salvările</string>
|
||||
<string name="cloud_not_synced">Salvările nu au fost sincronizate</string>
|
||||
<string name="stores_eyebrow_installed">instalat</string>
|
||||
<string name="stores_eyebrow_library">în biblioteca ta</string>
|
||||
<string name="stores_eyebrow_store">magazin</string>
|
||||
@@ -2365,7 +2352,7 @@
|
||||
<string name="touch_done">Terminat</string>
|
||||
<string name="touch_save_failed">Aranjamentul tactil nu a putut fi salvat</string>
|
||||
<string name="touch_saved">Aranjamentul tactil a fost salvat în Steam</string>
|
||||
<string name="touch_app_id">Aplicația %1$s</string>
|
||||
<string name="touch_app_id">Aplicația %1$d</string>
|
||||
<string name="touch_label_hint">Etichetă (opțional)</string>
|
||||
<string name="touch_icons_missing">Pictogramele Steam nu au fost găsite</string>
|
||||
<plurals name="touch_icons_found">
|
||||
@@ -2458,15 +2445,6 @@
|
||||
<string name="glasses_vr_fov">Câmp vizual SteamVR</string>
|
||||
<string name="glasses_vr_fov_hint">Mai mic aduce SteamVR mai aproape și umple ochelarii; mai mare arată mai mult din jurul tău.</string>
|
||||
<string name="glasses_playing">Se joacă pe %1$s</string>
|
||||
<string name="glasses_phone_recenter">Recentrează</string>
|
||||
<string name="glasses_phone_steamvr_menu">Meniu SteamVR</string>
|
||||
<string name="glasses_phone_switch_sticks">Stickuri: mâini ⇄ joc</string>
|
||||
<string name="glasses_phone_controllers">Controlere: %1$s</string>
|
||||
<string name="glasses_phone_keyboard">Tastatură</string>
|
||||
<string name="glasses_phone_session_menu">Meniul sesiunii</string>
|
||||
<string name="glasses_phone_stop_steamvr">Oprește SteamVR</string>
|
||||
<string name="glasses_phone_brightness">Luminozitate %1$d / %2$d</string>
|
||||
<string name="glasses_phone_battery">Baterie %1$d%% · %2$d °C</string>
|
||||
<string name="glasses_calibration">Calibrarea urmăririi</string>
|
||||
<string name="glasses_calibration_hint">Reduce deriva și aliniază referința de nivel.</string>
|
||||
<string name="glasses_calibration_saved_hint">Calibrarea salvată se aplică acestor ochelari.</string>
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
<string name="gesture_click_once">Один щелчок</string>
|
||||
<string name="gesture_hold_delay">Задержка удержания</string>
|
||||
<string name="gesture_double_delay">Интервал двойного касания</string>
|
||||
<string name="gesture_scope_game">Применяется к текущей игре</string>
|
||||
<string name="gesture_scope_default">Настройки по умолчанию для всех игр</string>
|
||||
<string name="gesture_reset">Восстановить исходные настройки жестов</string>
|
||||
<string name="gesture_cancel">Отмена</string>
|
||||
<string name="gesture_action">Выберите действие</string>
|
||||
@@ -35,16 +37,23 @@
|
||||
<string name="gesture_scroll">Колесо мыши</string>
|
||||
<string name="gesture_threshold">Порог жестов: %1$d пикс.</string>
|
||||
<string name="gesture_debug">Показывать распознанный жест</string>
|
||||
<string name="gesture_editor_subtitle">Управление мышью и клавиатурой по жестам</string>
|
||||
<string name="gesture_tab_one">Один палец</string>
|
||||
<string name="gesture_tab_two">Два пальца</string>
|
||||
<string name="gesture_tab_three">Три пальца</string>
|
||||
<string name="gesture_tab_options">Параметры</string>
|
||||
<string name="gesture_one_hint">Выберите действие для касания, перетаскивания или удержания.</string>
|
||||
<string name="gesture_two_hint">Настройте перемещение камеры, масштабирование и дополнительные щелчки.</string>
|
||||
<string name="gesture_three_hint">Держите дополнительные команды под рукой.</string>
|
||||
<string name="gesture_options_hint">Настройте движение, распознавание и визуальный отклик.</string>
|
||||
<string name="gesture_defaults">По умолчанию</string>
|
||||
<string name="gesture_game">Эта игра</string>
|
||||
<string name="gesture_use_defaults">Использовать настройки по умолчанию</string>
|
||||
<string name="gesture_inheriting">Используются настройки по умолчанию. Изменение создаст настройки для этой игры.</string>
|
||||
<string name="gesture_save_changes">Сохранить изменения</string>
|
||||
<string name="gesture_close">Закрыть</string>
|
||||
<string name="gesture_action_label">ДЕЙСТВИЕ</string>
|
||||
<string name="gesture_action_for">Для %1$s</string>
|
||||
<string name="gesture_search_actions">Поиск действий мыши или клавиш клавиатуры</string>
|
||||
<string name="gesture_clear_search">Очистить поиск</string>
|
||||
<string name="gesture_all_actions">Все</string>
|
||||
@@ -59,9 +68,12 @@
|
||||
<string name="gesture_group_numpad">Цифровой блок</string>
|
||||
<string name="gesture_group_symbols">Символы</string>
|
||||
<string name="gesture_movement">Движение мыши</string>
|
||||
<string name="gesture_movement_hint">Прямое перетаскивание следует позиции касания. Относительное следует движению пальца и полезно, когда игра возвращает мышь в центр.</string>
|
||||
<string name="gesture_middle_relative_hint">Панорамирование средней кнопкой всегда следует движению пальца, чтобы избежать скачков курсора.</string>
|
||||
<string name="gesture_direct">Прямой</string>
|
||||
<string name="gesture_relative">Относительное</string>
|
||||
<string name="gesture_recognition">Распознавание жестов</string>
|
||||
<string name="gesture_threshold_hint">Пройдите это расстояние, прежде чем жест двумя или тремя пальцами станет перемещением камеры или масштабированием.</string>
|
||||
<string name="gesture_feedback">Визуальный отклик</string>
|
||||
<string name="gesture_mouse_behavior">Поведение кнопки мыши</string>
|
||||
<string name="gesture_ms">%1$d мс</string>
|
||||
|
||||
@@ -583,16 +583,6 @@
|
||||
<string name="drawer_prev_page">Предыдущая страница</string>
|
||||
<string name="drawer_next_page">Следующая страница</string>
|
||||
<string name="drawer_hud">HUD производительности</string>
|
||||
<string name="drawer_hud_fps">Частота кадров</string>
|
||||
<string name="drawer_hud_battery">Температура батареи</string>
|
||||
<string name="drawer_hud_graph">График производительности</string>
|
||||
<string name="drawer_hud_color">Цвет</string>
|
||||
<string name="drawer_hud_color_mint">Мята</string>
|
||||
<string name="drawer_hud_color_white">Белый</string>
|
||||
<string name="drawer_hud_color_sky">Небесный</string>
|
||||
<string name="drawer_hud_color_amber">Янтарный</string>
|
||||
<string name="drawer_hud_color_violet">Фиолетовый</string>
|
||||
<string name="drawer_hud_opacity">Непрозрачность</string>
|
||||
<string name="drawer_scaling">Режим масштабирования</string>
|
||||
<string name="drawer_scaling_sharpness">Резкость масштабирования</string>
|
||||
<string name="drawer_effects">Экранные эффекты</string>
|
||||
@@ -2075,9 +2065,6 @@
|
||||
<string name="cloud_last_sync">Последняя синхронизация: %1$s</string>
|
||||
<string name="cloud_never">Еще не синхронизировано</string>
|
||||
<string name="cloud_done">Файлов: %1$d</string>
|
||||
<string name="cloud_up_to_date">Уже актуально</string>
|
||||
<string name="cloud_signed_out">Войдите в магазин, чтобы синхронизировать сохранения</string>
|
||||
<string name="cloud_not_synced">Сохранения не синхронизированы</string>
|
||||
<string name="stores_eyebrow_installed">установлено</string>
|
||||
<string name="stores_eyebrow_library">в вашей библиотеке</string>
|
||||
<string name="stores_eyebrow_store">магазин</string>
|
||||
@@ -2382,7 +2369,7 @@
|
||||
<string name="touch_done">Готово</string>
|
||||
<string name="touch_save_failed">Не удалось сохранить сенсорную раскладку</string>
|
||||
<string name="touch_saved">Сенсорная раскладка сохранена в Steam</string>
|
||||
<string name="touch_app_id">Приложение %1$s</string>
|
||||
<string name="touch_app_id">Приложение %1$d</string>
|
||||
<string name="touch_label_hint">Подпись (необязательно)</string>
|
||||
<string name="touch_icons_missing">Значки Steam не найдены</string>
|
||||
<plurals name="touch_icons_found">
|
||||
@@ -2477,15 +2464,6 @@
|
||||
<string name="glasses_vr_fov">Поле зрения SteamVR</string>
|
||||
<string name="glasses_vr_fov_hint">Меньше — SteamVR ближе и заполняет очки; больше — шире обзор вокруг.</string>
|
||||
<string name="glasses_playing">Игра на %1$s</string>
|
||||
<string name="glasses_phone_recenter">Перецентрировать</string>
|
||||
<string name="glasses_phone_steamvr_menu">Меню SteamVR</string>
|
||||
<string name="glasses_phone_switch_sticks">Стики: руки ⇄ игра</string>
|
||||
<string name="glasses_phone_controllers">Контроллеры: %1$s</string>
|
||||
<string name="glasses_phone_keyboard">Клавиатура</string>
|
||||
<string name="glasses_phone_session_menu">Меню сеанса</string>
|
||||
<string name="glasses_phone_stop_steamvr">Остановить SteamVR</string>
|
||||
<string name="glasses_phone_brightness">Яркость %1$d / %2$d</string>
|
||||
<string name="glasses_phone_battery">Батарея %1$d%% · %2$d °C</string>
|
||||
<string name="glasses_calibration">Калибровка отслеживания</string>
|
||||
<string name="glasses_calibration_hint">Уменьшает дрейф и выравнивает опорный горизонт.</string>
|
||||
<string name="glasses_calibration_saved_hint">Для этих очков применяется сохранённая калибровка.</string>
|
||||
|
||||
Loaded 100 of 172 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user