This commit is contained in:
commit
3e98dd1e7e
48 files changed
+13025
No files matched your search
@@ -0,0 +1,2 @@
|
|||||||
|
# Auto detect text files and perform LF normalization
|
||||||
|
* text=auto
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ko_fi: felitendo
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
name: 💡 Feature request
|
||||||
|
description: An idea to make rdfeed better.
|
||||||
|
labels:
|
||||||
|
- enhancement
|
||||||
|
body:
|
||||||
|
- type: checkboxes
|
||||||
|
id: checks
|
||||||
|
attributes:
|
||||||
|
label: Before you start
|
||||||
|
options:
|
||||||
|
- label: I searched the issues and this is not requested yet.
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: problem
|
||||||
|
attributes:
|
||||||
|
label: What problem would this solve?
|
||||||
|
description: What are you trying to do, and what gets in the way?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: idea
|
||||||
|
attributes:
|
||||||
|
label: What would you like?
|
||||||
|
description: How it could work. A rough idea is fine.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: alternatives
|
||||||
|
attributes:
|
||||||
|
label: Other ways you thought of
|
||||||
|
- type: textarea
|
||||||
|
id: more
|
||||||
|
attributes:
|
||||||
|
label: Anything else?
|
||||||
|
description: Mockups, screenshots, or other tools that do it well.
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
name: 🐛 Bug report
|
||||||
|
description: Something does not work as it should.
|
||||||
|
labels:
|
||||||
|
- bug
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: Thanks for taking the time! The more you fill in, the faster it can be fixed. Please leave out your company's server names and your user name.
|
||||||
|
- type: checkboxes
|
||||||
|
id: checks
|
||||||
|
attributes:
|
||||||
|
label: Before you start
|
||||||
|
options:
|
||||||
|
- label: I searched the issues and this is not reported yet.
|
||||||
|
required: true
|
||||||
|
- label: I use the latest version.
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: what
|
||||||
|
attributes:
|
||||||
|
label: What happened?
|
||||||
|
description: What did you do, and what went wrong?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: steps
|
||||||
|
attributes:
|
||||||
|
label: Steps to reproduce
|
||||||
|
placeholder: |
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
- type: textarea
|
||||||
|
id: expected
|
||||||
|
attributes:
|
||||||
|
label: What did you expect?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: where
|
||||||
|
attributes:
|
||||||
|
label: Where does it happen?
|
||||||
|
multiple: true
|
||||||
|
options:
|
||||||
|
- Adding a workspace
|
||||||
|
- Starting an app
|
||||||
|
- The app window (drawing, keyboard, size)
|
||||||
|
- MFA prompts
|
||||||
|
- The app menu
|
||||||
|
- The menu
|
||||||
|
- Other
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: mfa
|
||||||
|
attributes:
|
||||||
|
label: Does your company use MFA?
|
||||||
|
options:
|
||||||
|
- "Yes"
|
||||||
|
- "No"
|
||||||
|
- I don't know
|
||||||
|
- type: input
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: Version
|
||||||
|
description: The output of `rdfeed --version` and `xfreerdp3 /version`.
|
||||||
|
placeholder: rdfeed 1.0.0, FreeRDP 3.32.1
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: install
|
||||||
|
attributes:
|
||||||
|
label: How did you install it?
|
||||||
|
options:
|
||||||
|
- AUR (Arch, CachyOS, EndeavourOS, Manjaro)
|
||||||
|
- Built from source
|
||||||
|
- Other
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: system
|
||||||
|
attributes:
|
||||||
|
label: System
|
||||||
|
description: Distribution, desktop and its version, Wayland or X11.
|
||||||
|
placeholder: CachyOS, KDE Plasma 6.7, Wayland
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: logs
|
||||||
|
attributes:
|
||||||
|
label: Log
|
||||||
|
description: For a start that goes wrong, the log from `~/.cache/rdfeed/` (one file per app). Remove server names and user names first.
|
||||||
|
render: shell
|
||||||
|
- type: textarea
|
||||||
|
id: more
|
||||||
|
attributes:
|
||||||
|
label: Anything else?
|
||||||
|
description: Screenshots or anything else that could help.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
Executable
+36
@@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Refuses a release whose tag and Makefile disagree.
|
||||||
|
#
|
||||||
|
# The version is baked into the program at install time from the Makefile, and
|
||||||
|
# the packages take theirs from the same place. But the tag is what people see
|
||||||
|
# and what the release is named after. A tag that says something else produces
|
||||||
|
# a package called 1.0.4 containing a program that reports 1.0.3, and nothing
|
||||||
|
# would have complained.
|
||||||
|
#
|
||||||
|
# Anything that is not a v-tag (a run started by hand from a branch) is not a
|
||||||
|
# release and has nothing to check.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
here="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
ref="${1:-}"
|
||||||
|
|
||||||
|
case "$ref" in
|
||||||
|
v[0-9]*) ;;
|
||||||
|
*)
|
||||||
|
echo "not a release tag (${ref:-none}), nothing to check against"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
tag_version="${ref#v}"
|
||||||
|
make_version="$(make -s -C "$here" version)"
|
||||||
|
|
||||||
|
if [[ $tag_version != "$make_version" ]]; then
|
||||||
|
echo "tag $ref says $tag_version, the Makefile says $make_version" >&2
|
||||||
|
echo "Bump VERSION in the Makefile to match the tag, or retag." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$ref matches the Makefile"
|
||||||
Executable
+136
@@ -0,0 +1,136 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Builds the GitHub release notes for a tag, the way big projects such as
|
||||||
|
# Immich lay them out: the hand-written entry from CHANGELOG.md (a welcome,
|
||||||
|
# the highlights), a support section, then every commit since the last
|
||||||
|
# release, sorted by kind, and a link to the full changelog.
|
||||||
|
#
|
||||||
|
# .github/release-notes.sh v1.2.0 the notes
|
||||||
|
# .github/release-notes.sh --title v1.2.0 the title (checks the entry exists)
|
||||||
|
#
|
||||||
|
# Commit authors come from the GitHub API through gh. Without it the list
|
||||||
|
# goes without them.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.."
|
||||||
|
|
||||||
|
title=0
|
||||||
|
if [[ ${1:-} == --title ]]; then
|
||||||
|
title=1
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
tag="${1:?usage: release-notes.sh [--title] vX.Y.Z}"
|
||||||
|
|
||||||
|
if ! grep -qx "## $tag" CHANGELOG.md; then
|
||||||
|
echo "CHANGELOG.md has no entry for $tag. Add \"## $tag\" first." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if (( title )); then
|
||||||
|
printf '%s\n' "$tag"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
repo="${GITHUB_REPOSITORY:-$(git remote get-url origin | sed -E 's#^.*github\.com[:/]##; s#\.git$##')}"
|
||||||
|
name="${repo#*/}"
|
||||||
|
|
||||||
|
# The entry: up to the next one, without the date line (GitHub shows the
|
||||||
|
# date), one heading level up, outside code blocks.
|
||||||
|
entry="$(awk -v h="## $tag" '
|
||||||
|
$0 == h { on = 1; next }
|
||||||
|
on && /^## / { exit }
|
||||||
|
!on { next }
|
||||||
|
/^```/ { code = !code }
|
||||||
|
!code && /^_[0-9]{4}-[0-9]{2}-[0-9]{2}_$/ { next }
|
||||||
|
!code && /^###/ { sub(/^#/, "") }
|
||||||
|
{ print }
|
||||||
|
' CHANGELOG.md | sed -e '/./,$!d')"
|
||||||
|
|
||||||
|
# The commits: from the last release, or from the start. A tag that does not
|
||||||
|
# exist yet is a preview of what HEAD would become.
|
||||||
|
if git rev-parse -q --verify "refs/tags/$tag" > /dev/null; then
|
||||||
|
target="$tag"
|
||||||
|
prev="$(git describe --tags --abbrev=0 --match 'v[0-9]*' "$tag^" 2>/dev/null || true)"
|
||||||
|
else
|
||||||
|
target="$(git rev-parse HEAD)"
|
||||||
|
prev="$(git describe --tags --abbrev=0 --match 'v[0-9]*' HEAD 2>/dev/null || true)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
declare -A login=()
|
||||||
|
if command -v gh > /dev/null; then
|
||||||
|
if [[ -n $prev ]]; then
|
||||||
|
api=(api "repos/$repo/compare/$prev...$target" --jq '.commits[] | [.sha, (.author.login // "")] | @tsv')
|
||||||
|
else
|
||||||
|
api=(api --paginate "repos/$repo/commits?sha=$target&per_page=100" --jq '.[] | [.sha, (.author.login // "")] | @tsv')
|
||||||
|
fi
|
||||||
|
while IFS=$'\t' read -r sha who; do
|
||||||
|
[[ -n $who ]] && login[$sha]="$who"
|
||||||
|
done < <(gh "${api[@]}" 2>/dev/null || true)
|
||||||
|
fi
|
||||||
|
|
||||||
|
declare -A list=()
|
||||||
|
count_maint=0
|
||||||
|
while IFS=$'\t' read -r sha subject body; do
|
||||||
|
# Version bumps say nothing a reader needs.
|
||||||
|
[[ $subject =~ ^(chore:\ )?($name\ )?v?[0-9]+\.[0-9]+\.[0-9]+$ ]] && continue
|
||||||
|
|
||||||
|
case "$subject" in
|
||||||
|
*!:*) kind=breaking ;;
|
||||||
|
feat:* | feat\(*) kind=feat ;;
|
||||||
|
fix:* | fix\(*) kind=fix ;;
|
||||||
|
perf:* | perf\(*) kind=enh ;;
|
||||||
|
docs:* | docs\(*) kind=docs ;;
|
||||||
|
chore* | ci:* | ci\(* | build* | refactor* | test* | style*) kind=maint ;;
|
||||||
|
# Older commits without a prefix, sorted by what they say.
|
||||||
|
*README* | *readme* | *Readme*) kind=docs ;;
|
||||||
|
Add\ * | Put\ * | Introduce\ *) kind=feat ;;
|
||||||
|
Fix\ * | Repair\ * | Recover\ * | Stop\ *) kind=fix ;;
|
||||||
|
*\ *) kind=enh ;;
|
||||||
|
*) kind=maint ;;
|
||||||
|
esac
|
||||||
|
[[ $body == *"BREAKING CHANGE"* ]] && kind=breaking
|
||||||
|
[[ $subject == "Initial commit" ]] && kind=maint
|
||||||
|
|
||||||
|
line="* $subject"
|
||||||
|
[[ -n ${login[$sha]:-} ]] && line+=" by @${login[$sha]}"
|
||||||
|
line+=" in https://github.com/$repo/commit/$sha"
|
||||||
|
list[$kind]+="$line"$'\n'
|
||||||
|
[[ $kind == maint ]] && count_maint=$((count_maint + 1))
|
||||||
|
done < <(git log --reverse --no-merges --format='%H%x09%s%x09%b%x1e' "${prev:+$prev..}$target" | tr '\n\036' ' \n' | sed 's/^ //')
|
||||||
|
|
||||||
|
changes=''
|
||||||
|
for pair in "breaking:🚨 Breaking Changes" "feat:🚀 Features" "enh:🌟 Enhancements" "fix:🐛 Bug fixes" "docs:📚 Documentation"; do
|
||||||
|
kind="${pair%%:*}"
|
||||||
|
[[ -n ${list[$kind]:-} ]] || continue
|
||||||
|
changes+="### ${pair#*:}"$'\n'"${list[$kind]}"
|
||||||
|
done
|
||||||
|
if [[ -n ${list[maint]:-} ]]; then
|
||||||
|
changes+=$'\n'"<details><summary>🧰 Maintenance ($count_maint)</summary>"$'\n\n'"${list[maint]}"$'\n'"</details>"$'\n'
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n $prev ]]; then
|
||||||
|
full="**Full Changelog**: https://github.com/$repo/compare/$prev...$tag"
|
||||||
|
else
|
||||||
|
full="**Full Changelog**: https://github.com/$repo/commits/$tag"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# A release with highlights is a big one: it gets a heading and the support
|
||||||
|
# section, as in Immich. A patch is a sentence or two and the list.
|
||||||
|
if grep -q '^## Highlights$' <<< "$entry"; then
|
||||||
|
cat <<EOF
|
||||||
|
# 🚀 $name $tag
|
||||||
|
|
||||||
|
$entry
|
||||||
|
|
||||||
|
## ☕ Support $name
|
||||||
|
|
||||||
|
If $name is useful to you, you can buy me a coffee. It keeps these tools going. Found a bug or have an idea? Tell me in the [issues](https://github.com/$repo/issues).
|
||||||
|
|
||||||
|
<a href="https://ko-fi.com/felitendo"><img src="https://storage.ko-fi.com/cdn/kofi5.png?v=6" alt="Buy me a coffee on Ko-fi" height="48"></a>
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
EOF
|
||||||
|
else
|
||||||
|
printf '%s\n\n' "$entry"
|
||||||
|
fi
|
||||||
|
printf "## What's Changed\n%s\n\n%s\n" "$changes" "$full"
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
name: check
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
name: tests and shellcheck
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# Arch has FreeRDP 3, which the hook test loads.
|
||||||
|
container: archlinux:latest
|
||||||
|
steps:
|
||||||
|
- name: Install the tools
|
||||||
|
run: |
|
||||||
|
pacman -Syu --noconfirm --needed git base-devel python freerdp shared-mime-info \
|
||||||
|
gettext scdoc shellcheck desktop-file-utils
|
||||||
|
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
|
- run: make check
|
||||||
|
|
||||||
|
# NTLM against the spec's test vectors, the feed, the .rdp files, the
|
||||||
|
# icons, a fake RD Web server, the hook and the command end to end.
|
||||||
|
- run: make test
|
||||||
|
|
||||||
|
- name: Build the catalogs and the man page
|
||||||
|
run: make build
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
name: release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags: ['v*']
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
name: Test and release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container: archlinux:latest
|
||||||
|
steps:
|
||||||
|
- name: Install the tools
|
||||||
|
run: |
|
||||||
|
pacman -Syu --noconfirm --needed git base-devel python freerdp shared-mime-info \
|
||||||
|
gettext scdoc github-cli
|
||||||
|
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
# All tags, for the link to the changes since the last release.
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Check the tag against the Makefile
|
||||||
|
run: .github/check-version.sh "${{ github.ref_name }}"
|
||||||
|
|
||||||
|
- name: Check CHANGELOG.md has this release
|
||||||
|
run: .github/release-notes.sh --title "${{ github.ref_name }}"
|
||||||
|
|
||||||
|
- run: make test
|
||||||
|
|
||||||
|
- name: Create the release
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
run: |
|
||||||
|
git config --global --add safe.directory "$PWD"
|
||||||
|
.github/release-notes.sh "${{ github.ref_name }}" > notes.md
|
||||||
|
gh release create "${{ github.ref_name }}" \
|
||||||
|
--title "$(.github/release-notes.sh --title "${{ github.ref_name }}")" \
|
||||||
|
--notes-file notes.md
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# build artifacts
|
||||||
|
build/
|
||||||
|
po/*.mo
|
||||||
|
doc/*.1
|
||||||
|
__pycache__/
|
||||||
|
|
||||||
|
# packages
|
||||||
|
dist/
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
What each release brings, newest first. The [GitHub releases](https://github.com/LoonixTools/rdfeed/releases) add every commit that went into it.
|
||||||
|
|
||||||
|
## v1.0.0
|
||||||
|
|
||||||
|
_2026-10-02_
|
||||||
|
|
||||||
|
Welcome to the very first release of rdfeed! It brings the RemoteApps of your company to Linux: add the workspace once, and Word, Excel and the other apps show up in your app menu and open as windows of their own.
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img width="480" alt="The rdfeed menu in Konsole with the Contoso Apps workspace" src="https://raw.githubusercontent.com/LoonixTools/rdfeed/v1.0.0/res/screenshots/menu.png">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
### Highlights
|
||||||
|
|
||||||
|
- The apps of your company in the app menu
|
||||||
|
- Open your files in them with "Open with"
|
||||||
|
- Signs in like Windows, also with a work e-mail address
|
||||||
|
- Works with MFA on the gateway
|
||||||
|
- Sharp on scaled screens, with the right keyboard layout
|
||||||
|
- New apps show up by themselves every day
|
||||||
|
- The password stays in the keyring
|
||||||
|
- In 14 languages
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
## Style
|
||||||
|
|
||||||
|
- Keep everything short: replies, explanations, comments, docs.
|
||||||
|
- Use simple English: short sentences, common words.
|
||||||
|
- Avoid em dashes (—). Do not just swap them for "-" either. Rewrite the sentence instead, for
|
||||||
|
example with a comma, a colon, brackets or two sentences.
|
||||||
|
|
||||||
|
## Commits
|
||||||
|
|
||||||
|
- English only.
|
||||||
|
- Conventional Commits prefix: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:`, `ci:`, `build:`.
|
||||||
|
- Subject as short as possible. Imperative, lowercase, no trailing period.
|
||||||
|
- Body only when something genuinely cannot be inferred from the diff.
|
||||||
|
- Never add `Co-Authored-By`, "Generated with" or any other AI attribution to commits or PR descriptions.
|
||||||
|
- Do not commit or push before I have checked the changes locally and said they are fine. Then
|
||||||
|
commit and push. Several attempts at the same thing make one commit, and attempts that did not
|
||||||
|
work make none. Different things done in one session get a commit each. This also goes for
|
||||||
|
releases and tags.
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
- `src/rdfeed` and `src/lib/*.sh`: the command and its menu, plain bash.
|
||||||
|
- `src/fetch/rdfeed-fetch`: signs in to the feed (NTLM) and downloads the apps. Python, standard
|
||||||
|
library only.
|
||||||
|
- `src/hook/rdfeed-hook.c`: goes into FreeRDP with `LD_PRELOAD`. Waits before the second gateway
|
||||||
|
sign-in so the second MFA prompt comes, and keeps GFX off.
|
||||||
|
- `res/systemd`: the daily refresh, a user timer.
|
||||||
|
|
||||||
|
## Releases
|
||||||
|
|
||||||
|
Releases look like the ones of big projects such as Immich. `.github/release-notes.sh <tag>` builds
|
||||||
|
the notes: the entry from `CHANGELOG.md` (welcome and highlights), a support section, every commit
|
||||||
|
since the last release sorted by its prefix (`feat`, `fix`, `docs`, ...) with author and link, and
|
||||||
|
the full changelog link. So commit subjects end up in public: keep them clear.
|
||||||
|
|
||||||
|
1. Read `git log <last tag>..HEAD` and pick the version: only fixes → patch, something new → minor,
|
||||||
|
something that breaks or needs the user to act → major.
|
||||||
|
2. Bump the version and add the entry at the top of `CHANGELOG.md`, in one commit (`chore: 1.4.0`).
|
||||||
|
3. Push, then push the tag: `git tag v1.4.0 && git push origin main v1.4.0`. The `release` workflow
|
||||||
|
runs the tests and creates the release. It stops when the entry is missing.
|
||||||
|
4. PKGBUILDS picks up the new release for the AUR on its own.
|
||||||
|
|
||||||
|
A minor or major release (has `### Highlights`, gets a heading and the support section):
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## v1.4.0
|
||||||
|
|
||||||
|
_2026-09-24_
|
||||||
|
|
||||||
|
Welcome to rdfeed `v1.4.0`! One or two sentences on what this release is about.
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img width="480" alt="What the picture shows" src="https://raw.githubusercontent.com/LoonixTools/rdfeed/v1.4.0/<path>">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
### 🚨 Breaking changes
|
||||||
|
|
||||||
|
- Only if there are any: what changed, and what the user has to do.
|
||||||
|
|
||||||
|
### Highlights
|
||||||
|
|
||||||
|
- First highlight, a few words
|
||||||
|
- Second highlight
|
||||||
|
```
|
||||||
|
|
||||||
|
A picture under the welcome is optional. To show the menu or another screen of the program, use a
|
||||||
|
real screenshot of it running in Konsole, in English. Never a text copy of the screen. The same goes
|
||||||
|
for the README. The highlights stay a plain list: no heading or text per highlight, the list of
|
||||||
|
commits explains the rest.
|
||||||
|
|
||||||
|
A patch release is just a sentence or two, for example: "A small patch. The menu no longer closes
|
||||||
|
when you press Enter." The list of commits follows on its own.
|
||||||
|
|
||||||
|
- Write for users: what they notice, not how the code does it. Friendly and simple.
|
||||||
|
- Commands and settings they type go in backticks, buttons and labels in bold.
|
||||||
|
- To change an old release: edit its entry, commit, then
|
||||||
|
`gh release edit <tag> --title <tag> --notes "$(.github/release-notes.sh <tag>)"`.
|
||||||
|
|
||||||
|
## README
|
||||||
|
|
||||||
|
- New UI (a window, a screen, a menu, a setting, a notification) gets a screenshot in the README,
|
||||||
|
next to the text about it. Like for releases: a real screenshot of it running, in English.
|
||||||
|
- When a screen changes, take its screenshot again. The README never shows an old one.
|
||||||
|
- Screenshots show the demo workspace (Contoso Apps, `CONTOSO\jdoe`) from `tests/fake_rdweb.py`,
|
||||||
|
never a real company.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Never test against the real setup: the workspaces, the keyring, the timer and the app menu belong
|
||||||
|
to the user's machine, and a real server counts every wrong password.
|
||||||
|
|
||||||
|
- `make test` runs NTLM against the test vectors of MS-NLMP, the feed, the .rdp files and the
|
||||||
|
icons, whole subscriptions against `tests/fake_rdweb.py`, the hook, and the command from `add` to
|
||||||
|
`remove`. No real server, no root.
|
||||||
|
- `tests/test_cli.sh` runs in a temporary home, with fakes for `secret-tool`, `systemctl`,
|
||||||
|
`notify-send`, `kbuildsycoca6` and `xfreerdp3` on `PATH`. Do the same for anything by hand.
|
||||||
|
- `python3 tests/fake_rdweb.py [port]` serves the demo workspace (user `jdoe`, domain `CONTOSO`,
|
||||||
|
the password is in the file). Point a temporary home at it with `XDG_CONFIG_HOME`,
|
||||||
|
`XDG_DATA_HOME`, `XDG_CACHE_HOME` and `RF_LIBDIR=src/lib RF_FETCH=src/fetch/rdfeed-fetch`.
|
||||||
|
- `make check` after every change. New strings: `po/update-pot.sh`, then translate them in every `po/*.po`.
|
||||||
@@ -0,0 +1,674 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you
|
||||||
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
|
or can get the source code. And you must show them these terms so they
|
||||||
|
know their rights.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
|
that there is no warranty for this free software. For both users' and
|
||||||
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the special requirements of the GNU Affero General Public License,
|
||||||
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short
|
||||||
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
<program> Copyright (C) <year> <name of author>
|
||||||
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
# rdfeed: build and install
|
||||||
|
#
|
||||||
|
# The shell front end and rdfeed-fetch install as they are. The one thing to
|
||||||
|
# compile is rdfeed-hook.so, which goes into FreeRDP. Translations and the
|
||||||
|
# man page are optional and skipped when msgfmt or scdoc are missing.
|
||||||
|
|
||||||
|
# Overridable so a packager can pass the version it is actually building
|
||||||
|
# (`make VERSION=$pkgver`). The literal below is the fallback for builds
|
||||||
|
# straight from a checkout, and is what a release tag has to carry.
|
||||||
|
VERSION ?= 1.0.0
|
||||||
|
|
||||||
|
PREFIX ?= /usr
|
||||||
|
DESTDIR ?=
|
||||||
|
BINDIR ?= $(PREFIX)/bin
|
||||||
|
DATADIR ?= $(PREFIX)/share
|
||||||
|
LIBDIR ?= $(DATADIR)/rdfeed/lib
|
||||||
|
LIBEXECDIR ?= $(PREFIX)/lib/rdfeed
|
||||||
|
LOCALEDIR ?= $(DATADIR)/locale
|
||||||
|
MANDIR ?= $(DATADIR)/man
|
||||||
|
ICONDIR ?= $(DATADIR)/icons/hicolor/scalable/apps
|
||||||
|
|
||||||
|
# Where systemd looks for user units, asked of systemd itself for a normal
|
||||||
|
# install. A build with a prefix of its own keeps them under that prefix.
|
||||||
|
ifeq ($(PREFIX),/usr)
|
||||||
|
USERUNITDIR ?= $(shell pkg-config --variable=systemduserunitdir systemd 2>/dev/null || echo /usr/lib/systemd/user)
|
||||||
|
else
|
||||||
|
USERUNITDIR ?= $(DATADIR)/systemd/user
|
||||||
|
endif
|
||||||
|
|
||||||
|
CC ?= cc
|
||||||
|
CFLAGS ?= -O2 -Wall -Wextra
|
||||||
|
BUILDDIR ?= build
|
||||||
|
HOOK := $(BUILDDIR)/rdfeed-hook.so
|
||||||
|
|
||||||
|
LINGUAS := de es fr it ja ko nl pl pt_BR ru tr uk zh_CN
|
||||||
|
MOFILES := $(patsubst %,po/%.mo,$(LINGUAS))
|
||||||
|
MANPAGE := doc/rdfeed.1
|
||||||
|
|
||||||
|
LIBS := $(wildcard src/lib/*.sh)
|
||||||
|
|
||||||
|
MSGFMT := $(shell command -v msgfmt 2>/dev/null)
|
||||||
|
SCDOC := $(shell command -v scdoc 2>/dev/null)
|
||||||
|
|
||||||
|
.PHONY: all build check test install uninstall clean version
|
||||||
|
|
||||||
|
all: build
|
||||||
|
|
||||||
|
build: $(HOOK) $(MOFILES) $(MANPAGE)
|
||||||
|
|
||||||
|
# The one place the version is written down, for everything that has to agree
|
||||||
|
# with it: the release workflow checking that the tag it was handed says the
|
||||||
|
# same thing, and the PKGBUILD.
|
||||||
|
version:
|
||||||
|
@echo $(VERSION)
|
||||||
|
|
||||||
|
$(HOOK): src/hook/rdfeed-hook.c
|
||||||
|
@mkdir -p $(BUILDDIR)
|
||||||
|
$(CC) $(CFLAGS) $(CPPFLAGS) -shared -fPIC $(LDFLAGS) -o $@ $< -ldl
|
||||||
|
|
||||||
|
po/%.mo: po/%.po
|
||||||
|
ifdef MSGFMT
|
||||||
|
$(MSGFMT) --check --output-file=$@ $<
|
||||||
|
else
|
||||||
|
@echo "msgfmt not found, skipping $@"
|
||||||
|
endif
|
||||||
|
|
||||||
|
$(MANPAGE): doc/rdfeed.1.scd
|
||||||
|
ifdef SCDOC
|
||||||
|
$(SCDOC) < $< > $@
|
||||||
|
else
|
||||||
|
@echo "scdoc not found, skipping $@"
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Syntax-check every shell file and rdfeed-fetch, and run shellcheck when it
|
||||||
|
# is available. SC2034 is off: the files share their variables, and
|
||||||
|
# shellcheck looks at one file at a time.
|
||||||
|
check:
|
||||||
|
@set -e; for f in src/rdfeed $(LIBS) tests/*.sh; do \
|
||||||
|
bash -n "$$f" && echo "ok $$f"; \
|
||||||
|
done
|
||||||
|
@python3 -m py_compile src/fetch/rdfeed-fetch tests/*.py && echo "ok python"
|
||||||
|
@rm -rf src/fetch/__pycache__ tests/__pycache__
|
||||||
|
@if command -v shellcheck >/dev/null 2>&1; then \
|
||||||
|
shellcheck -x -e SC1090,SC1091,SC2034 src/rdfeed $(LIBS) tests/*.sh \
|
||||||
|
&& echo "ok shellcheck"; \
|
||||||
|
else \
|
||||||
|
echo "shellcheck not found, skipped"; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The NTLM sign-in against the test vectors of the spec, the feed, the .rdp
|
||||||
|
# files and the icons, a whole subscription against a fake RD Web server, and
|
||||||
|
# the hook. Nothing here talks to a real server or touches the real setup.
|
||||||
|
test: $(HOOK)
|
||||||
|
python3 -m unittest discover -s tests -p 'test_*.py' -v
|
||||||
|
RDFEED_HOOK=$(HOOK) bash tests/test_hook.sh
|
||||||
|
bash tests/test_cli.sh
|
||||||
|
|
||||||
|
install: build
|
||||||
|
# the command
|
||||||
|
install -Dm755 src/rdfeed "$(DESTDIR)$(BINDIR)/rdfeed"
|
||||||
|
install -d "$(DESTDIR)$(LIBDIR)"
|
||||||
|
install -Dm644 -t "$(DESTDIR)$(LIBDIR)" $(LIBS)
|
||||||
|
sed -i -e 's|@VERSION@|$(VERSION)|g' \
|
||||||
|
-e 's|@LIBDIR@|$(LIBDIR)|g' \
|
||||||
|
-e 's|@LIBEXECDIR@|$(LIBEXECDIR)|g' \
|
||||||
|
-e 's|@LOCALEDIR@|$(LOCALEDIR)|g' \
|
||||||
|
"$(DESTDIR)$(BINDIR)/rdfeed" \
|
||||||
|
"$(DESTDIR)$(LIBDIR)"/*.sh
|
||||||
|
|
||||||
|
# the helpers
|
||||||
|
install -Dm755 src/fetch/rdfeed-fetch "$(DESTDIR)$(LIBEXECDIR)/rdfeed-fetch"
|
||||||
|
install -Dm755 $(HOOK) "$(DESTDIR)$(LIBEXECDIR)/rdfeed-hook.so"
|
||||||
|
|
||||||
|
# the daily refresh, and the icon of the workspace folders
|
||||||
|
install -Dm644 res/systemd/rdfeed-refresh.service "$(DESTDIR)$(USERUNITDIR)/rdfeed-refresh.service"
|
||||||
|
install -Dm644 res/systemd/rdfeed-refresh.timer "$(DESTDIR)$(USERUNITDIR)/rdfeed-refresh.timer"
|
||||||
|
sed -i -e 's|@BINDIR@|$(BINDIR)|g' "$(DESTDIR)$(USERUNITDIR)/rdfeed-refresh.service"
|
||||||
|
install -Dm644 res/rdfeed.svg "$(DESTDIR)$(ICONDIR)/rdfeed.svg"
|
||||||
|
|
||||||
|
# translations
|
||||||
|
@for l in $(LINGUAS); do \
|
||||||
|
if [ -f "po/$$l.mo" ]; then \
|
||||||
|
install -Dm644 "po/$$l.mo" \
|
||||||
|
"$(DESTDIR)$(LOCALEDIR)/$$l/LC_MESSAGES/rdfeed.mo"; \
|
||||||
|
fi; \
|
||||||
|
done
|
||||||
|
|
||||||
|
# documentation
|
||||||
|
@if [ -f $(MANPAGE) ]; then \
|
||||||
|
install -Dm644 $(MANPAGE) "$(DESTDIR)$(MANDIR)/man1/rdfeed.1"; \
|
||||||
|
fi
|
||||||
|
install -Dm644 README.md "$(DESTDIR)$(DATADIR)/doc/rdfeed/README.md"
|
||||||
|
|
||||||
|
uninstall:
|
||||||
|
rm -f "$(DESTDIR)$(BINDIR)/rdfeed"
|
||||||
|
rm -rf "$(DESTDIR)$(DATADIR)/rdfeed"
|
||||||
|
rm -rf "$(DESTDIR)$(LIBEXECDIR)"
|
||||||
|
rm -f "$(DESTDIR)$(USERUNITDIR)/rdfeed-refresh.service"
|
||||||
|
rm -f "$(DESTDIR)$(USERUNITDIR)/rdfeed-refresh.timer"
|
||||||
|
rm -f "$(DESTDIR)$(ICONDIR)/rdfeed.svg"
|
||||||
|
rm -f "$(DESTDIR)$(MANDIR)/man1/rdfeed.1"
|
||||||
|
rm -rf "$(DESTDIR)$(DATADIR)/doc/rdfeed"
|
||||||
|
@for l in $(LINGUAS); do rm -f "$(DESTDIR)$(LOCALEDIR)/$$l/LC_MESSAGES/rdfeed.mo"; done
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf $(BUILDDIR) po/*.mo $(MANPAGE)
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
<p align="center">
|
||||||
|
<img width="200" src="res/rdfeed.svg" alt="rdfeed">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h1 align="center">rdfeed</h1>
|
||||||
|
|
||||||
|
<h3 align="center">The RemoteApps of your company, in the Linux app menu.</h3>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
Word, Excel and the other Windows apps your company publishes open as windows of their own.<br>
|
||||||
|
Like "Work Resources" on Windows, through FreeRDP.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h5 align="center">
|
||||||
|
<a href="#install">Install</a> |
|
||||||
|
<a href="#how-to-use">How to use</a> |
|
||||||
|
<a href="#is-it-safe">Is it safe?</a> |
|
||||||
|
<a href="https://github.com/LoonixTools/rdfeed/issues">Report a bug</a>
|
||||||
|
</h5>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://ko-fi.com/felitendo"><img src="https://storage.ko-fi.com/cdn/kofi5.png?v=6" alt="Buy me a coffee on Ko-fi" height="48"></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="res/screenshots/menu.png" alt="The rdfeed menu in Konsole: one workspace, Contoso Apps, with 4 apps for CONTOSO\jdoe" width="560">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>Arch</b>, CachyOS, EndeavourOS, Manjaro</summary>
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yay -S rdfeed
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Other distributions</summary>
|
||||||
|
|
||||||
|
See [Build from source](#more). It needs FreeRDP 3, Python 3, bash and
|
||||||
|
`secret-tool` (libsecret).
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rdfeed
|
||||||
|
```
|
||||||
|
|
||||||
|
Press **1** and type the address your IT department gave you. The server
|
||||||
|
name or your work e-mail address often works too. Then sign in with your
|
||||||
|
Windows user name and password.
|
||||||
|
|
||||||
|
The apps show up in your app menu, in a folder with the name of the
|
||||||
|
workspace. Click one and it opens in a window of its own. If your company uses
|
||||||
|
MFA, confirm the sign-in in your authenticator app. Many servers ask twice: a
|
||||||
|
notification tells you when the second prompt is coming.
|
||||||
|
|
||||||
|
Files on your computer open in the apps too: right-click a `.docx` and pick
|
||||||
|
**Open with**, then Word.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Settings</summary>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="res/screenshots/settings.png" alt="The settings in Konsole: share a folder, open files with the apps, size, sound, graphics, the wait for the second MFA prompt, and a daily refresh, with the selected one explained at the bottom" width="640">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## Is it safe?
|
||||||
|
|
||||||
|
Your password goes into the keyring, not into a file. FreeRDP gets it through
|
||||||
|
a pipe, so it never shows up in the list of running programs.
|
||||||
|
|
||||||
|
The connection files from the server can ask to share every drive, camera and
|
||||||
|
USB device of your computer. rdfeed removes that. The apps only see the folder
|
||||||
|
you pick under **Share a folder**, and none by default. When you open a file in
|
||||||
|
an app, its folder is shared while that app runs.
|
||||||
|
|
||||||
|
## More
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>What it fixes in FreeRDP</summary>
|
||||||
|
|
||||||
|
Starting a RemoteApp with plain `xfreerdp3` often goes wrong. rdfeed takes care of it:
|
||||||
|
|
||||||
|
- **It hangs after the MFA prompt.** A connection broker needs a second sign-in
|
||||||
|
at the gateway. Its prompt only comes if the client waits about ten seconds,
|
||||||
|
and FreeRDP does not wait. rdfeed does.
|
||||||
|
- **Y and Z are swapped.** Under Wayland, FreeRDP sees the wrong keyboard layout.
|
||||||
|
rdfeed asks the desktop.
|
||||||
|
- **The apps are blurry or tiny** with a scaled desktop. rdfeed has the server
|
||||||
|
draw them at your scale.
|
||||||
|
- **Signing in to the feed fails.** It needs NTLM, which `curl` no longer
|
||||||
|
always has. rdfeed signs in by itself.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Known problems</summary>
|
||||||
|
|
||||||
|
- **A window stays partly empty after you minimize or resize it.** This is a
|
||||||
|
bug in FreeRDP. Under **Settings**, **Graphics** on classic paints every
|
||||||
|
window right, but some servers end the session with it.
|
||||||
|
- **A second app of the same workspace takes over the first one's connection.**
|
||||||
|
Its windows move into the new one, nothing closes.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>How it works</summary>
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| `rdfeed` | The menu and the commands. |
|
||||||
|
| `rdfeed-fetch` | Signs in to the feed and downloads the apps and their icons. |
|
||||||
|
| `rdfeed-hook.so` | Goes into FreeRDP: the wait for the second MFA prompt, and the classic graphics if you pick them. |
|
||||||
|
| `rdfeed-refresh.timer` | Loads the apps again once a day, so new ones show up. |
|
||||||
|
|
||||||
|
The feed is the one Windows subscribes to under *RemoteApp and Desktop
|
||||||
|
Connections* (`https://server/RDWeb/Feed/webfeed.aspx`). Azure Virtual Desktop
|
||||||
|
and Windows 365 sign in another way and are not supported. All details:
|
||||||
|
`man rdfeed`.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Build from source</summary>
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make
|
||||||
|
make test
|
||||||
|
sudo make install
|
||||||
|
```
|
||||||
|
|
||||||
|
Needs a C compiler, and for the translations and the man page gettext and
|
||||||
|
scdoc. To run it: bash, Python 3, FreeRDP 3 (`xfreerdp3` or `xfreerdp`) and
|
||||||
|
`secret-tool`.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
- [FreeRDP](https://www.freerdp.com/), which does the actual work.
|
||||||
|
- [MS-NLMP](https://learn.microsoft.com/openspecs/windows_protocols/ms-nlmp/), for NTLM and its test vectors.
|
||||||
|
|
||||||
|
GPL-3.0-or-later.
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
rdfeed(1)
|
||||||
|
|
||||||
|
# NAME
|
||||||
|
|
||||||
|
rdfeed - the RemoteApps of your company in the Linux app menu
|
||||||
|
|
||||||
|
# SYNOPSIS
|
||||||
|
|
||||||
|
*rdfeed* [_command_]
|
||||||
|
|
||||||
|
# DESCRIPTION
|
||||||
|
|
||||||
|
Companies publish their Windows apps (Word, Excel, their own tools) as an RD
|
||||||
|
Web Access feed. Windows subscribes to it under "RemoteApp and Desktop
|
||||||
|
Connections" and puts the apps in the start menu, in a folder with the name of
|
||||||
|
the workspace. rdfeed does the same on Linux: each app gets an entry in the
|
||||||
|
app menu and opens as a window of its own, not as a whole Windows desktop.
|
||||||
|
The apps run on the company's servers, through FreeRDP.
|
||||||
|
|
||||||
|
Run without a command it shows an interactive menu. Everything it can be told
|
||||||
|
is reachable from there; the files behind it do not need to be edited by
|
||||||
|
hand.
|
||||||
|
|
||||||
|
# COMMANDS
|
||||||
|
|
||||||
|
*add* [_address_] [*--user* _name_]
|
||||||
|
Add a workspace. The address is the one of the feed, as Windows takes it
|
||||||
|
(*https://server/RDWeb/Feed/webfeed.aspx*), or just the server name, or
|
||||||
|
the RD Web Access page, or a work e-mail address. For an e-mail address
|
||||||
|
the feed is looked up in the TXT record *\_msradc.*_domain_, as Windows
|
||||||
|
does it. The user name can be _DOMAIN\\name_, _name@domain_, or just the
|
||||||
|
name, which takes the server's own domain.
|
||||||
|
|
||||||
|
Without a terminal, the user name and the password are read from stdin,
|
||||||
|
one per line.
|
||||||
|
|
||||||
|
*refresh* [_workspace_]
|
||||||
|
Load the apps of every workspace again, or of the one given. Apps that
|
||||||
|
were added show up in the app menu, apps that were taken away disappear.
|
||||||
|
|
||||||
|
*list*
|
||||||
|
The workspaces with their apps, each as _workspace/app_, the name *run*
|
||||||
|
takes.
|
||||||
|
|
||||||
|
*run* _app_ [_file_]
|
||||||
|
Start an app. _app_ is enough when only one workspace has an app of that
|
||||||
|
name, otherwise _workspace/app_. This is what the app menu entries run.
|
||||||
|
|
||||||
|
With a file, the app opens it. The folder of the file is shared as a
|
||||||
|
drive for that session, and the app is started with the path on it,
|
||||||
|
_\\\\tsclient\\folder\\file_, the way Windows does it. If the server
|
||||||
|
does not let the app take a file this way, it opens empty, and the file
|
||||||
|
is on that drive.
|
||||||
|
|
||||||
|
*remove* _workspace_
|
||||||
|
Remove a workspace: its app menu entries, its folder, the downloaded
|
||||||
|
files, the saved password.
|
||||||
|
|
||||||
|
*-h*, *--help*
|
||||||
|
Show the help.
|
||||||
|
|
||||||
|
*-V*, *--version*
|
||||||
|
Show the version.
|
||||||
|
|
||||||
|
# SETTINGS
|
||||||
|
|
||||||
|
*Share a folder*
|
||||||
|
Off, Documents, Downloads or the home folder. The apps see it as a drive
|
||||||
|
and can open and save files there. Off by default: then they only see the
|
||||||
|
files on the server.
|
||||||
|
|
||||||
|
*Open files with the apps*
|
||||||
|
On by default. The app menu entries name the file types of each app, so
|
||||||
|
file managers offer it under "Open with", for example Word for .docx
|
||||||
|
files. The types come from the feed when the administrator entered them,
|
||||||
|
as Windows takes them. Most feeds have none, and then rdfeed knows the
|
||||||
|
usual ones: Word, Excel, PowerPoint, OneNote, Outlook, Visio, Access,
|
||||||
|
Project, Publisher, PDF readers, AutoCAD and DWG TrueView, Notepad. Your
|
||||||
|
default apps stay as they are.
|
||||||
|
|
||||||
|
*Size*
|
||||||
|
How big the apps are drawn on the server. Automatic takes the scale your
|
||||||
|
desktop gives X11 programs that scale themselves (KDE Plasma's Xwayland
|
||||||
|
scale, or Xft.dpi), so the apps come out sharp and the right size.
|
||||||
|
|
||||||
|
*Sound*
|
||||||
|
Play the sound of the apps on this computer.
|
||||||
|
|
||||||
|
*Graphics*
|
||||||
|
GFX (the default) or classic. See *FREERDP PROBLEMS*.
|
||||||
|
|
||||||
|
*Wait for the second MFA prompt*
|
||||||
|
Automatic (the default), always or never. See *FREERDP PROBLEMS*.
|
||||||
|
|
||||||
|
*Refresh the apps every day*
|
||||||
|
A systemd user timer (*rdfeed-refresh.timer*) that loads the apps again
|
||||||
|
once a day, as Windows does. It is switched on with the first workspace.
|
||||||
|
When the password stops working it says so in a notification.
|
||||||
|
|
||||||
|
# FREERDP PROBLEMS
|
||||||
|
|
||||||
|
rdfeed starts each app with *xfreerdp3* (*xfreerdp* on Fedora) in RemoteApp
|
||||||
|
mode, with *rdfeed-hook.so* loaded into it. The hook works around two
|
||||||
|
problems that leave FreeRDP hanging or windows half painted:
|
||||||
|
|
||||||
|
*The second MFA prompt*
|
||||||
|
Most companies run a connection broker, which sends the client on to the
|
||||||
|
server that hosts the session. FreeRDP then opens a second tunnel through
|
||||||
|
the RD Gateway at once. With MFA on the gateway (the Azure MFA extension
|
||||||
|
of NPS), that second sign-in needs a second prompt in the authenticator
|
||||||
|
app. When it comes less than about ten seconds after the first one was
|
||||||
|
confirmed, no prompt is sent and the gateway waits forever. So the hook
|
||||||
|
waits 12 seconds before a new gateway connection, and a notification says
|
||||||
|
a second prompt is coming. Automatic waits only when the first sign-in
|
||||||
|
took long enough for somebody to confirm a prompt.
|
||||||
|
|
||||||
|
*Windows that stay empty*
|
||||||
|
In RemoteApp mode with the graphics pipeline (GFX), FreeRDP may not
|
||||||
|
repaint a window after it was minimized or resized. FreeRDP 3 has no
|
||||||
|
switch to turn GFX off. With *Graphics* on classic, the hook keeps it off
|
||||||
|
and every window paints right. But some servers end the session right
|
||||||
|
after signing in without GFX, so GFX stays the default. Not fixed yet.
|
||||||
|
|
||||||
|
# HOW SAFE IS THIS
|
||||||
|
|
||||||
|
The password goes into the keyring (the Secret Service, as GNOME Keyring or
|
||||||
|
KWallet provide it), never into a file. FreeRDP gets it on stdin, never on its
|
||||||
|
command line, so it shows in no process list.
|
||||||
|
|
||||||
|
The .rdp files from the server can ask to share every drive, camera and USB
|
||||||
|
device of this computer. rdfeed removes those lines. Only the folder picked
|
||||||
|
under *Share a folder* is shared, and the folder of a file you open in an app,
|
||||||
|
for as long as that session runs.
|
||||||
|
|
||||||
|
The server's certificate is trusted the first time and checked after that
|
||||||
|
(*/cert:tofu*). The feed itself is fetched over HTTPS with the system's
|
||||||
|
certificates.
|
||||||
|
|
||||||
|
# FILES
|
||||||
|
|
||||||
|
_~/.config/rdfeed/config_
|
||||||
|
The settings.
|
||||||
|
|
||||||
|
_~/.config/rdfeed/workspaces/_
|
||||||
|
One file per workspace: the address, the name, the user.
|
||||||
|
|
||||||
|
_~/.local/share/rdfeed/_
|
||||||
|
The apps of each workspace: the .rdp files and the icons.
|
||||||
|
|
||||||
|
_~/.local/share/applications/rdfeed-\*.desktop_
|
||||||
|
The app menu entries, with a folder per workspace in
|
||||||
|
_~/.config/menus/applications-merged/_.
|
||||||
|
|
||||||
|
_~/.cache/rdfeed/_
|
||||||
|
The log of the last start of each app, for when something goes wrong.
|
||||||
|
|
||||||
|
# LIMITS
|
||||||
|
|
||||||
|
Only feeds that sign in with Windows (NTLM) authentication, which is what RD
|
||||||
|
Web Access uses. Azure Virtual Desktop and Windows 365 sign in with Microsoft
|
||||||
|
Entra and are not supported.
|
||||||
|
|
||||||
|
Starting a second app of the same workspace opens a second connection, which
|
||||||
|
takes over the session of the first: its windows move into the new one.
|
||||||
|
Nothing closes.
|
||||||
|
|
||||||
|
# SEE ALSO
|
||||||
|
|
||||||
|
*xfreerdp3*(1), *secret-tool*(1)
|
||||||
|
|
||||||
|
# AUTHORS
|
||||||
|
|
||||||
|
Felitendo. Source and bug reports: https://github.com/LoonixTools/rdfeed
|
||||||
@@ -0,0 +1,564 @@
|
|||||||
|
# German translation for rdfeed.
|
||||||
|
# Copyright (C) 2026 Felitendo
|
||||||
|
# This file is distributed under the same license as rdfeed.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: rdfeed 1.0.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: https://github.com/LoonixTools/rdfeed/issues\n"
|
||||||
|
"POT-Creation-Date: 2026-10-02 10:29+0200\n"
|
||||||
|
"PO-Revision-Date: 2026-10-02 09:30+0200\n"
|
||||||
|
"Last-Translator: Felitendo\n"
|
||||||
|
"Language-Team: German\n"
|
||||||
|
"Language: de\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: src/rdfeed:79 src/rdfeed:103
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not write to %s."
|
||||||
|
msgstr "Konnte nicht nach %s schreiben."
|
||||||
|
|
||||||
|
#: src/rdfeed:82
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Signing in to %s…"
|
||||||
|
msgstr "Anmeldung bei %s…"
|
||||||
|
|
||||||
|
#: src/rdfeed:100
|
||||||
|
msgid ""
|
||||||
|
"The password could not be saved in the keyring, so each app asks for it."
|
||||||
|
msgstr ""
|
||||||
|
"Das Passwort konnte nicht im Schlüsselbund gespeichert werden, darum fragt "
|
||||||
|
"jede App danach."
|
||||||
|
|
||||||
|
#: src/rdfeed:111
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "1 app from %s is now in your app menu."
|
||||||
|
msgstr "1 App von %s ist jetzt in deinem Anwendungsmenü."
|
||||||
|
|
||||||
|
#: src/rdfeed:113
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d apps from %s are now in your app menu."
|
||||||
|
msgstr "%d Apps von %s sind jetzt in deinem Anwendungsmenü."
|
||||||
|
|
||||||
|
#: src/rdfeed:129
|
||||||
|
msgid ""
|
||||||
|
"Your IT department has the address. A work e-mail address often works too."
|
||||||
|
msgstr ""
|
||||||
|
"Die Adresse bekommst du von deiner IT-Abteilung. Oft klappt auch deine "
|
||||||
|
"Firmen-E-Mail-Adresse."
|
||||||
|
|
||||||
|
#: src/rdfeed:130
|
||||||
|
msgid "Feed address, server or e-mail:"
|
||||||
|
msgstr "Feed-Adresse, Server oder E-Mail:"
|
||||||
|
|
||||||
|
#: src/rdfeed:137
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No feed is registered for %s. Ask your IT department for the address."
|
||||||
|
msgstr ""
|
||||||
|
"Für %s ist kein Feed hinterlegt. Frag deine IT-Abteilung nach der Adresse."
|
||||||
|
|
||||||
|
#: src/rdfeed:142 src/rdfeed:156
|
||||||
|
msgid "User name (DOMAIN\\name or name@domain):"
|
||||||
|
msgstr "Benutzername (DOMÄNE\\name oder name@domäne):"
|
||||||
|
|
||||||
|
#: src/rdfeed:146 src/rdfeed:159
|
||||||
|
msgid "Password:"
|
||||||
|
msgstr "Passwort:"
|
||||||
|
|
||||||
|
#: src/rdfeed:178 src/rdfeed:236
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No workspace yet. Add one with `%s add`."
|
||||||
|
msgstr "Noch kein Arbeitsbereich. Füge einen mit `%s add` hinzu."
|
||||||
|
|
||||||
|
#: src/rdfeed:187 src/lib/launch.sh:220
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No password saved for %s. Sign in again in rdfeed."
|
||||||
|
msgstr "Für %s ist kein Passwort gespeichert. Melde dich in rdfeed neu an."
|
||||||
|
|
||||||
|
#: src/rdfeed:191 src/lib/launch.sh:149
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Password for %s:"
|
||||||
|
msgstr "Passwort für %s:"
|
||||||
|
|
||||||
|
#: src/rdfeed:195
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Refreshing %s…"
|
||||||
|
msgstr "%s wird aktualisiert…"
|
||||||
|
|
||||||
|
#: src/rdfeed:205
|
||||||
|
msgid "The password no longer works. Sign in again in rdfeed."
|
||||||
|
msgstr "Das Passwort funktioniert nicht mehr. Melde dich in rdfeed neu an."
|
||||||
|
|
||||||
|
#: src/rdfeed:214
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s: %d apps"
|
||||||
|
msgstr "%s: %d Apps"
|
||||||
|
|
||||||
|
#: src/rdfeed:222
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Which workspace? See `%s list` for the names."
|
||||||
|
msgstr "Welcher Arbeitsbereich? Die Namen zeigt `%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:229
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s and its apps are removed."
|
||||||
|
msgstr "%s und seine Apps sind entfernt."
|
||||||
|
|
||||||
|
#: src/rdfeed:253
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Which app? See `%s list`."
|
||||||
|
msgstr "Welche App? Siehe `%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:268 src/lib/launch.sh:199
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "There is no app %s. See `%s list`."
|
||||||
|
msgstr "Es gibt keine App %s. Siehe `%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:270
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Several workspaces have %s. Write it as WORKSPACE/APP, see `%s list`."
|
||||||
|
msgstr ""
|
||||||
|
"Mehrere Arbeitsbereiche haben %s. Schreib es als ARBEITSBEREICH/APP, siehe "
|
||||||
|
"`%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:278
|
||||||
|
msgid "Usage: rdfeed [command]"
|
||||||
|
msgstr "Aufruf: rdfeed [Befehl]"
|
||||||
|
|
||||||
|
#: src/rdfeed:280
|
||||||
|
msgid "Commands:"
|
||||||
|
msgstr "Befehle:"
|
||||||
|
|
||||||
|
#: src/rdfeed:281
|
||||||
|
msgid "Add the workspace of your company"
|
||||||
|
msgstr "Den Arbeitsbereich deiner Firma hinzufügen"
|
||||||
|
|
||||||
|
#: src/rdfeed:282
|
||||||
|
msgid "Load the apps again"
|
||||||
|
msgstr "Die Apps neu laden"
|
||||||
|
|
||||||
|
#: src/rdfeed:283
|
||||||
|
msgid "List the workspaces and their apps"
|
||||||
|
msgstr "Arbeitsbereiche und ihre Apps auflisten"
|
||||||
|
|
||||||
|
#: src/rdfeed:284
|
||||||
|
msgid "Start an app, or open a file with it"
|
||||||
|
msgstr "Eine App starten oder eine Datei damit öffnen"
|
||||||
|
|
||||||
|
#: src/rdfeed:285
|
||||||
|
msgid "Remove a workspace and its apps"
|
||||||
|
msgstr "Einen Arbeitsbereich mit seinen Apps entfernen"
|
||||||
|
|
||||||
|
#: src/rdfeed:286
|
||||||
|
msgid "Show this help"
|
||||||
|
msgstr "Diese Hilfe anzeigen"
|
||||||
|
|
||||||
|
#: src/rdfeed:287
|
||||||
|
msgid "Show the version"
|
||||||
|
msgstr "Die Version anzeigen"
|
||||||
|
|
||||||
|
#: src/rdfeed:289
|
||||||
|
msgid "Without a command an interactive menu is shown."
|
||||||
|
msgstr "Ohne Befehl wird ein interaktives Menü angezeigt."
|
||||||
|
|
||||||
|
#: src/rdfeed:313
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Unknown command: %s"
|
||||||
|
msgstr "Unbekannter Befehl: %s"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:174 src/lib/menu.sh:251
|
||||||
|
msgid "never"
|
||||||
|
msgstr "nie"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:184
|
||||||
|
msgid "just now"
|
||||||
|
msgstr "gerade eben"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:186
|
||||||
|
msgid "1 minute ago"
|
||||||
|
msgstr "vor 1 Minute"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:189
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d minutes ago"
|
||||||
|
msgstr "vor %d Minuten"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:191
|
||||||
|
msgid "1 hour ago"
|
||||||
|
msgstr "vor 1 Stunde"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:194
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d hours ago"
|
||||||
|
msgstr "vor %d Stunden"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:196
|
||||||
|
msgid "1 day ago"
|
||||||
|
msgstr "vor 1 Tag"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:199
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d days ago"
|
||||||
|
msgstr "vor %d Tagen"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:169
|
||||||
|
msgid ""
|
||||||
|
"Your password has expired. Change it on Windows or on the RD Web page, then "
|
||||||
|
"sign in again in rdfeed."
|
||||||
|
msgstr ""
|
||||||
|
"Dein Passwort ist abgelaufen. Ändere es unter Windows oder auf der RD-Web-"
|
||||||
|
"Seite und melde dich dann in rdfeed neu an."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:171
|
||||||
|
msgid "Your account is locked. Ask your IT department."
|
||||||
|
msgstr "Dein Konto ist gesperrt. Frag deine IT-Abteilung."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:173
|
||||||
|
msgid "The server did not accept the password. Sign in again in rdfeed."
|
||||||
|
msgstr ""
|
||||||
|
"Der Server hat das Passwort nicht angenommen. Melde dich in rdfeed neu an."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:175
|
||||||
|
msgid "Could not reach the server. Check the internet connection."
|
||||||
|
msgstr "Der Server ist nicht erreichbar. Prüf die Internetverbindung."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:178
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "FreeRDP stopped: %s"
|
||||||
|
msgstr "FreeRDP hat aufgegeben: %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:205 src/lib/launch.sh:206
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "File not found: %s"
|
||||||
|
msgstr "Datei nicht gefunden: %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:212 src/lib/launch.sh:213
|
||||||
|
msgid ""
|
||||||
|
"FreeRDP 3 is missing. Install it to start the apps (the package is called "
|
||||||
|
"freerdp)."
|
||||||
|
msgstr ""
|
||||||
|
"FreeRDP 3 fehlt. Installiere es, um die Apps zu starten (das Paket heißt "
|
||||||
|
"freerdp)."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:277
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Opening %s in %s"
|
||||||
|
msgstr "%s wird in %s geöffnet"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:278
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "If it does not open by itself, open it in the app from the drive %s."
|
||||||
|
msgstr ""
|
||||||
|
"Falls sie sich nicht von selbst öffnet, öffne sie in der App vom Laufwerk %s."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:280
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Starting %s"
|
||||||
|
msgstr "%s wird gestartet"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:281
|
||||||
|
msgid ""
|
||||||
|
"If your company uses MFA, confirm the sign-in in your authenticator app."
|
||||||
|
msgstr ""
|
||||||
|
"Wenn deine Firma MFA nutzt, bestätige die Anmeldung in deiner Authenticator-"
|
||||||
|
"App."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:290
|
||||||
|
msgid "A second sign-in is coming"
|
||||||
|
msgstr "Gleich kommt eine zweite Anmeldung"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:291
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid ""
|
||||||
|
"Confirm the second prompt in your authenticator app too. It comes in about "
|
||||||
|
"%d seconds."
|
||||||
|
msgstr ""
|
||||||
|
"Bestätige auch die zweite Anfrage in deiner Authenticator-App. Sie kommt in "
|
||||||
|
"etwa %d Sekunden."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:301
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s could not start"
|
||||||
|
msgstr "%s konnte nicht starten"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:303
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Log: %s"
|
||||||
|
msgstr "Protokoll: %s"
|
||||||
|
|
||||||
|
#. TRANSLATORS: the letter after "[" is the key for yes. y works too.
|
||||||
|
#: src/lib/menu.sh:93
|
||||||
|
msgid "[y/N]"
|
||||||
|
msgstr "[j/N]"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:163
|
||||||
|
msgid "1 app"
|
||||||
|
msgstr "1 App"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:165
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d apps"
|
||||||
|
msgstr "%d Apps"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:190
|
||||||
|
msgid "No workspace yet. Add the one of your company with [1]."
|
||||||
|
msgstr "Noch kein Arbeitsbereich. Füge den deiner Firma mit [1] hinzu."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:233
|
||||||
|
msgid "off"
|
||||||
|
msgstr "aus"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:234
|
||||||
|
msgid "home folder"
|
||||||
|
msgstr "Persönlicher Ordner"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:237
|
||||||
|
msgid "Documents"
|
||||||
|
msgstr "Dokumente"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:238
|
||||||
|
msgid "Downloads"
|
||||||
|
msgstr "Downloads"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:243
|
||||||
|
msgid "not found"
|
||||||
|
msgstr "nicht gefunden"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:246
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "automatic (%d %%)"
|
||||||
|
msgstr "automatisch (%d %%)"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:248
|
||||||
|
msgid "classic"
|
||||||
|
msgstr "klassisch"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:249
|
||||||
|
msgid "GFX"
|
||||||
|
msgstr "GFX"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:250
|
||||||
|
msgid "always"
|
||||||
|
msgstr "immer"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:252
|
||||||
|
msgid "automatic"
|
||||||
|
msgstr "automatisch"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:261
|
||||||
|
msgid ""
|
||||||
|
"The apps only see the files on the server. Pick a folder to open and save "
|
||||||
|
"files of this computer in them."
|
||||||
|
msgstr ""
|
||||||
|
"Die Apps sehen nur die Dateien auf dem Server. Wähl einen Ordner, damit sie "
|
||||||
|
"Dateien dieses Computers öffnen und speichern können."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:262
|
||||||
|
msgid ""
|
||||||
|
"The apps can open and save files in this folder. It shows up as a drive in "
|
||||||
|
"them."
|
||||||
|
msgstr ""
|
||||||
|
"Die Apps können in diesem Ordner Dateien öffnen und speichern. Er erscheint "
|
||||||
|
"in ihnen als Laufwerk."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:263
|
||||||
|
msgid ""
|
||||||
|
"Offers the apps under \"Open with\" for their file types, for example Word "
|
||||||
|
"for .docx. The folder of the file is shared with the app while it is open."
|
||||||
|
msgstr ""
|
||||||
|
"Bietet die Apps unter „Öffnen mit“ für ihre Dateitypen an, zum Beispiel Word "
|
||||||
|
"für .docx. Der Ordner der Datei wird freigegeben, solange sie offen ist."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:264
|
||||||
|
msgid ""
|
||||||
|
"How big the apps are drawn. Automatic follows the scale of your desktop, so "
|
||||||
|
"they are sharp and the right size."
|
||||||
|
msgstr ""
|
||||||
|
"Wie groß die Apps gezeichnet werden. Automatisch folgt der Skalierung deines "
|
||||||
|
"Desktops, so sind sie scharf und richtig groß."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:265
|
||||||
|
msgid "Plays the sound of the apps here, for calls and videos."
|
||||||
|
msgstr "Spielt den Ton der Apps hier ab, für Anrufe und Videos."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:266
|
||||||
|
msgid ""
|
||||||
|
"Paints every window right. But some servers end the session right after "
|
||||||
|
"signing in, then go back to GFX."
|
||||||
|
msgstr "Zeichnet jedes Fenster richtig. Manche Server beenden die Sitzung aber direkt nach der Anmeldung, dann zurück auf GFX."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:267
|
||||||
|
msgid ""
|
||||||
|
"Works with every server. FreeRDP may not repaint a window after it was "
|
||||||
|
"minimized or resized."
|
||||||
|
msgstr "Geht mit jedem Server. FreeRDP zeichnet ein Fenster nach dem Minimieren oder Größe-Ändern eventuell nicht neu."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:268
|
||||||
|
msgid ""
|
||||||
|
"Always waits before the second sign-in. For when the second prompt does not "
|
||||||
|
"come with automatic."
|
||||||
|
msgstr ""
|
||||||
|
"Wartet immer vor der zweiten Anmeldung. Für den Fall, dass mit automatisch "
|
||||||
|
"keine zweite Anfrage kommt."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:269
|
||||||
|
msgid "Never waits. Right when your company has no MFA."
|
||||||
|
msgstr "Wartet nie. Richtig, wenn deine Firma kein MFA nutzt."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:270
|
||||||
|
msgid ""
|
||||||
|
"Many servers with MFA need a second sign-in, and its prompt only comes after "
|
||||||
|
"a short wait. Automatic waits when the first sign-in took a while."
|
||||||
|
msgstr ""
|
||||||
|
"Viele Server mit MFA brauchen eine zweite Anmeldung, und deren Anfrage kommt "
|
||||||
|
"erst nach kurzem Warten. Automatisch wartet, wenn die erste Anmeldung etwas "
|
||||||
|
"gedauert hat."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:273
|
||||||
|
msgid ""
|
||||||
|
"Loads the list of apps again once a day, so new apps show up by themselves."
|
||||||
|
msgstr ""
|
||||||
|
"Lädt die Liste der Apps einmal am Tag neu, so tauchen neue Apps von selbst "
|
||||||
|
"auf."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:275 src/lib/menu.sh:330
|
||||||
|
msgid "Needs a systemd user session."
|
||||||
|
msgstr "Braucht eine systemd-Benutzersitzung."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:322 src/lib/menu.sh:325
|
||||||
|
msgid "Could not save the setting."
|
||||||
|
msgstr "Einstellung konnte nicht gespeichert werden."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:371 src/lib/menu.sh:609
|
||||||
|
msgid "Settings"
|
||||||
|
msgstr "Einstellungen"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:372
|
||||||
|
msgid "↑↓ select ←→ or Space: change q: back"
|
||||||
|
msgstr "↑↓ auswählen ←→ oder Leertaste: ändern q: zurück"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:373
|
||||||
|
msgid "ON"
|
||||||
|
msgstr "AN"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:374
|
||||||
|
msgid "OFF"
|
||||||
|
msgstr "AUS"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:456 src/lib/menu.sh:608
|
||||||
|
msgid "Workspaces"
|
||||||
|
msgstr "Arbeitsbereiche"
|
||||||
|
|
||||||
|
#. TRANSLATORS: keep the letters, they are the keys.
|
||||||
|
#: src/lib/menu.sh:458
|
||||||
|
msgid ""
|
||||||
|
"↑↓ select r: refresh s: sign in again d: remove a: add q: back"
|
||||||
|
msgstr ""
|
||||||
|
"↑↓ auswählen r: aktualisieren s: neu anmelden d: entfernen a: "
|
||||||
|
"hinzufügen q: zurück"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:459
|
||||||
|
msgid "No workspace yet. Press a to add one."
|
||||||
|
msgstr "Noch kein Arbeitsbereich. Drück a, um einen hinzuzufügen."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:484
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "refreshed %s"
|
||||||
|
msgstr "aktualisiert %s"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:512
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Remove \"%s\" and its apps?"
|
||||||
|
msgstr "„%s“ und seine Apps entfernen?"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:536 src/lib/menu.sh:607
|
||||||
|
msgid "Start an app"
|
||||||
|
msgstr "Eine App starten"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:537
|
||||||
|
msgid "↑↓ select Enter: start q: back"
|
||||||
|
msgstr "↑↓ auswählen Enter: starten q: zurück"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:538
|
||||||
|
msgid "No apps yet. Add a workspace first."
|
||||||
|
msgstr "Noch keine Apps. Füge zuerst einen Arbeitsbereich hinzu."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:579
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s is starting. It opens in a window of its own."
|
||||||
|
msgstr "%s startet. Es öffnet sich in einem eigenen Fenster."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:606
|
||||||
|
msgid "Add a workspace"
|
||||||
|
msgstr "Arbeitsbereich hinzufügen"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:610
|
||||||
|
msgid "Quit"
|
||||||
|
msgstr "Beenden"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:163
|
||||||
|
msgid "The server did not accept the user name or password."
|
||||||
|
msgstr "Der Server hat Benutzername oder Passwort nicht angenommen."
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:164
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not reach the server: %s"
|
||||||
|
msgstr "Server nicht erreichbar: %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:165
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "There is no RD Web feed at %s."
|
||||||
|
msgstr "Unter %s gibt es keinen RD-Web-Feed."
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:166
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not load the apps: %s"
|
||||||
|
msgstr "Die Apps konnten nicht geladen werden: %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:282
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Remote desktop of %s"
|
||||||
|
msgstr "Remotedesktop von %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:284
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s from %s"
|
||||||
|
msgstr "%s von %s"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Apps"
|
||||||
|
msgstr "Apps"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Share a folder"
|
||||||
|
msgstr "Ordner freigeben"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Open files with the apps"
|
||||||
|
msgstr "Dateien mit den Apps öffnen"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Size"
|
||||||
|
msgstr "Größe"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Sound"
|
||||||
|
msgstr "Ton"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Graphics"
|
||||||
|
msgstr "Grafik"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Signing in"
|
||||||
|
msgstr "Anmeldung"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Wait for the second MFA prompt"
|
||||||
|
msgstr "Auf zweite MFA-Anfrage warten"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Refresh the apps every day"
|
||||||
|
msgstr "Apps täglich aktualisieren"
|
||||||
@@ -0,0 +1,565 @@
|
|||||||
|
# Spanish translation for rdfeed.
|
||||||
|
# Copyright (C) 2026 Felitendo
|
||||||
|
# This file is distributed under the same license as rdfeed.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: rdfeed 1.0.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: https://github.com/LoonixTools/rdfeed/issues\n"
|
||||||
|
"POT-Creation-Date: 2026-10-02 10:29+0200\n"
|
||||||
|
"PO-Revision-Date: 2026-10-02 09:30+0200\n"
|
||||||
|
"Last-Translator: Felitendo\n"
|
||||||
|
"Language-Team: Spanish\n"
|
||||||
|
"Language: es\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: src/rdfeed:79 src/rdfeed:103
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not write to %s."
|
||||||
|
msgstr "No se pudo escribir en %s."
|
||||||
|
|
||||||
|
#: src/rdfeed:82
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Signing in to %s…"
|
||||||
|
msgstr "Iniciando sesión en %s…"
|
||||||
|
|
||||||
|
#: src/rdfeed:100
|
||||||
|
msgid ""
|
||||||
|
"The password could not be saved in the keyring, so each app asks for it."
|
||||||
|
msgstr ""
|
||||||
|
"No se pudo guardar la contraseña en el llavero, así que cada app la pedirá."
|
||||||
|
|
||||||
|
#: src/rdfeed:111
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "1 app from %s is now in your app menu."
|
||||||
|
msgstr "1 app de %s ya está en tu menú de aplicaciones."
|
||||||
|
|
||||||
|
#: src/rdfeed:113
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d apps from %s are now in your app menu."
|
||||||
|
msgstr "%d apps de %s ya están en tu menú de aplicaciones."
|
||||||
|
|
||||||
|
#: src/rdfeed:129
|
||||||
|
msgid ""
|
||||||
|
"Your IT department has the address. A work e-mail address often works too."
|
||||||
|
msgstr ""
|
||||||
|
"Tu departamento de TI tiene la dirección. A menudo también sirve tu correo "
|
||||||
|
"del trabajo."
|
||||||
|
|
||||||
|
#: src/rdfeed:130
|
||||||
|
msgid "Feed address, server or e-mail:"
|
||||||
|
msgstr "Dirección del feed, servidor o correo:"
|
||||||
|
|
||||||
|
#: src/rdfeed:137
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No feed is registered for %s. Ask your IT department for the address."
|
||||||
|
msgstr ""
|
||||||
|
"No hay ningún feed registrado para %s. Pide la dirección a tu departamento "
|
||||||
|
"de TI."
|
||||||
|
|
||||||
|
#: src/rdfeed:142 src/rdfeed:156
|
||||||
|
msgid "User name (DOMAIN\\name or name@domain):"
|
||||||
|
msgstr "Nombre de usuario (DOMINIO\\nombre o nombre@dominio):"
|
||||||
|
|
||||||
|
#: src/rdfeed:146 src/rdfeed:159
|
||||||
|
msgid "Password:"
|
||||||
|
msgstr "Contraseña:"
|
||||||
|
|
||||||
|
#: src/rdfeed:178 src/rdfeed:236
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No workspace yet. Add one with `%s add`."
|
||||||
|
msgstr "Todavía no hay ningún espacio de trabajo. Añade uno con `%s add`."
|
||||||
|
|
||||||
|
#: src/rdfeed:187 src/lib/launch.sh:220
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No password saved for %s. Sign in again in rdfeed."
|
||||||
|
msgstr ""
|
||||||
|
"No hay ninguna contraseña guardada para %s. Vuelve a iniciar sesión en "
|
||||||
|
"rdfeed."
|
||||||
|
|
||||||
|
#: src/rdfeed:191 src/lib/launch.sh:149
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Password for %s:"
|
||||||
|
msgstr "Contraseña de %s:"
|
||||||
|
|
||||||
|
#: src/rdfeed:195
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Refreshing %s…"
|
||||||
|
msgstr "Actualizando %s…"
|
||||||
|
|
||||||
|
#: src/rdfeed:205
|
||||||
|
msgid "The password no longer works. Sign in again in rdfeed."
|
||||||
|
msgstr "La contraseña ya no funciona. Vuelve a iniciar sesión en rdfeed."
|
||||||
|
|
||||||
|
#: src/rdfeed:214
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s: %d apps"
|
||||||
|
msgstr "%s: %d apps"
|
||||||
|
|
||||||
|
#: src/rdfeed:222
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Which workspace? See `%s list` for the names."
|
||||||
|
msgstr "¿Qué espacio de trabajo? Los nombres están en `%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:229
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s and its apps are removed."
|
||||||
|
msgstr "%s y sus apps se han quitado."
|
||||||
|
|
||||||
|
#: src/rdfeed:253
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Which app? See `%s list`."
|
||||||
|
msgstr "¿Qué app? Mira `%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:268 src/lib/launch.sh:199
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "There is no app %s. See `%s list`."
|
||||||
|
msgstr "No existe la app %s. Mira `%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:270
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Several workspaces have %s. Write it as WORKSPACE/APP, see `%s list`."
|
||||||
|
msgstr ""
|
||||||
|
"Varios espacios de trabajo tienen %s. Escríbelo como ESPACIO/APP, mira `%s "
|
||||||
|
"list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:278
|
||||||
|
msgid "Usage: rdfeed [command]"
|
||||||
|
msgstr "Uso: rdfeed [comando]"
|
||||||
|
|
||||||
|
#: src/rdfeed:280
|
||||||
|
msgid "Commands:"
|
||||||
|
msgstr "Comandos:"
|
||||||
|
|
||||||
|
#: src/rdfeed:281
|
||||||
|
msgid "Add the workspace of your company"
|
||||||
|
msgstr "Añadir el espacio de trabajo de tu empresa"
|
||||||
|
|
||||||
|
#: src/rdfeed:282
|
||||||
|
msgid "Load the apps again"
|
||||||
|
msgstr "Volver a cargar las apps"
|
||||||
|
|
||||||
|
#: src/rdfeed:283
|
||||||
|
msgid "List the workspaces and their apps"
|
||||||
|
msgstr "Listar los espacios de trabajo y sus apps"
|
||||||
|
|
||||||
|
#: src/rdfeed:284
|
||||||
|
msgid "Start an app, or open a file with it"
|
||||||
|
msgstr "Abrir una app, o un archivo con ella"
|
||||||
|
|
||||||
|
#: src/rdfeed:285
|
||||||
|
msgid "Remove a workspace and its apps"
|
||||||
|
msgstr "Quitar un espacio de trabajo y sus apps"
|
||||||
|
|
||||||
|
#: src/rdfeed:286
|
||||||
|
msgid "Show this help"
|
||||||
|
msgstr "Mostrar esta ayuda"
|
||||||
|
|
||||||
|
#: src/rdfeed:287
|
||||||
|
msgid "Show the version"
|
||||||
|
msgstr "Mostrar la versión"
|
||||||
|
|
||||||
|
#: src/rdfeed:289
|
||||||
|
msgid "Without a command an interactive menu is shown."
|
||||||
|
msgstr "Sin comando se muestra un menú interactivo."
|
||||||
|
|
||||||
|
#: src/rdfeed:313
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Unknown command: %s"
|
||||||
|
msgstr "Comando desconocido: %s"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:174 src/lib/menu.sh:251
|
||||||
|
msgid "never"
|
||||||
|
msgstr "nunca"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:184
|
||||||
|
msgid "just now"
|
||||||
|
msgstr "ahora mismo"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:186
|
||||||
|
msgid "1 minute ago"
|
||||||
|
msgstr "hace 1 minuto"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:189
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d minutes ago"
|
||||||
|
msgstr "hace %d minutos"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:191
|
||||||
|
msgid "1 hour ago"
|
||||||
|
msgstr "hace 1 hora"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:194
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d hours ago"
|
||||||
|
msgstr "hace %d horas"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:196
|
||||||
|
msgid "1 day ago"
|
||||||
|
msgstr "hace 1 día"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:199
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d days ago"
|
||||||
|
msgstr "hace %d días"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:169
|
||||||
|
msgid ""
|
||||||
|
"Your password has expired. Change it on Windows or on the RD Web page, then "
|
||||||
|
"sign in again in rdfeed."
|
||||||
|
msgstr ""
|
||||||
|
"Tu contraseña ha caducado. Cámbiala en Windows o en la página de RD Web y "
|
||||||
|
"luego vuelve a iniciar sesión en rdfeed."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:171
|
||||||
|
msgid "Your account is locked. Ask your IT department."
|
||||||
|
msgstr "Tu cuenta está bloqueada. Pregunta a tu departamento de TI."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:173
|
||||||
|
msgid "The server did not accept the password. Sign in again in rdfeed."
|
||||||
|
msgstr ""
|
||||||
|
"El servidor no aceptó la contraseña. Vuelve a iniciar sesión en rdfeed."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:175
|
||||||
|
msgid "Could not reach the server. Check the internet connection."
|
||||||
|
msgstr "No se pudo conectar con el servidor. Comprueba la conexión a internet."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:178
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "FreeRDP stopped: %s"
|
||||||
|
msgstr "FreeRDP se detuvo: %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:205 src/lib/launch.sh:206
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "File not found: %s"
|
||||||
|
msgstr "No se encontró el archivo: %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:212 src/lib/launch.sh:213
|
||||||
|
msgid ""
|
||||||
|
"FreeRDP 3 is missing. Install it to start the apps (the package is called "
|
||||||
|
"freerdp)."
|
||||||
|
msgstr ""
|
||||||
|
"Falta FreeRDP 3. Instálalo para abrir las apps (el paquete se llama freerdp)."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:277
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Opening %s in %s"
|
||||||
|
msgstr "Abriendo %s en %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:278
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "If it does not open by itself, open it in the app from the drive %s."
|
||||||
|
msgstr "Si no se abre solo, ábrelo en la app desde la unidad %s."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:280
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Starting %s"
|
||||||
|
msgstr "Abriendo %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:281
|
||||||
|
msgid ""
|
||||||
|
"If your company uses MFA, confirm the sign-in in your authenticator app."
|
||||||
|
msgstr ""
|
||||||
|
"Si tu empresa usa MFA, confirma el inicio de sesión en tu app de "
|
||||||
|
"autenticación."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:290
|
||||||
|
msgid "A second sign-in is coming"
|
||||||
|
msgstr "Viene un segundo inicio de sesión"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:291
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid ""
|
||||||
|
"Confirm the second prompt in your authenticator app too. It comes in about "
|
||||||
|
"%d seconds."
|
||||||
|
msgstr ""
|
||||||
|
"Confirma también la segunda solicitud en tu app de autenticación. Llega en "
|
||||||
|
"unos %d segundos."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:301
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s could not start"
|
||||||
|
msgstr "%s no se pudo abrir"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:303
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Log: %s"
|
||||||
|
msgstr "Registro: %s"
|
||||||
|
|
||||||
|
#. TRANSLATORS: the letter after "[" is the key for yes. y works too.
|
||||||
|
#: src/lib/menu.sh:93
|
||||||
|
msgid "[y/N]"
|
||||||
|
msgstr "[s/N]"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:163
|
||||||
|
msgid "1 app"
|
||||||
|
msgstr "1 app"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:165
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d apps"
|
||||||
|
msgstr "%d apps"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:190
|
||||||
|
msgid "No workspace yet. Add the one of your company with [1]."
|
||||||
|
msgstr ""
|
||||||
|
"Todavía no hay ningún espacio de trabajo. Añade el de tu empresa con [1]."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:233
|
||||||
|
msgid "off"
|
||||||
|
msgstr "desactivado"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:234
|
||||||
|
msgid "home folder"
|
||||||
|
msgstr "carpeta personal"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:237
|
||||||
|
msgid "Documents"
|
||||||
|
msgstr "Documentos"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:238
|
||||||
|
msgid "Downloads"
|
||||||
|
msgstr "Descargas"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:243
|
||||||
|
msgid "not found"
|
||||||
|
msgstr "no encontrada"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:246
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "automatic (%d %%)"
|
||||||
|
msgstr "automático (%d %%)"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:248
|
||||||
|
msgid "classic"
|
||||||
|
msgstr "clásico"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:249
|
||||||
|
msgid "GFX"
|
||||||
|
msgstr "GFX"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:250
|
||||||
|
msgid "always"
|
||||||
|
msgstr "siempre"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:252
|
||||||
|
msgid "automatic"
|
||||||
|
msgstr "automático"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:261
|
||||||
|
msgid ""
|
||||||
|
"The apps only see the files on the server. Pick a folder to open and save "
|
||||||
|
"files of this computer in them."
|
||||||
|
msgstr ""
|
||||||
|
"Las apps solo ven los archivos del servidor. Elige una carpeta para que "
|
||||||
|
"puedan abrir y guardar archivos de este equipo."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:262
|
||||||
|
msgid ""
|
||||||
|
"The apps can open and save files in this folder. It shows up as a drive in "
|
||||||
|
"them."
|
||||||
|
msgstr ""
|
||||||
|
"Las apps pueden abrir y guardar archivos en esta carpeta. Aparece en ellas "
|
||||||
|
"como una unidad."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:263
|
||||||
|
msgid ""
|
||||||
|
"Offers the apps under \"Open with\" for their file types, for example Word "
|
||||||
|
"for .docx. The folder of the file is shared with the app while it is open."
|
||||||
|
msgstr ""
|
||||||
|
"Ofrece las apps en «Abrir con» para sus tipos de archivo, por ejemplo Word "
|
||||||
|
"para .docx. La carpeta del archivo se comparte mientras está abierto."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:264
|
||||||
|
msgid ""
|
||||||
|
"How big the apps are drawn. Automatic follows the scale of your desktop, so "
|
||||||
|
"they are sharp and the right size."
|
||||||
|
msgstr ""
|
||||||
|
"Qué tamaño tienen las apps. Automático sigue la escala de tu escritorio, así "
|
||||||
|
"se ven nítidas y del tamaño correcto."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:265
|
||||||
|
msgid "Plays the sound of the apps here, for calls and videos."
|
||||||
|
msgstr "Reproduce aquí el sonido de las apps, para llamadas y vídeos."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:266
|
||||||
|
msgid ""
|
||||||
|
"Paints every window right. But some servers end the session right after "
|
||||||
|
"signing in, then go back to GFX."
|
||||||
|
msgstr "Dibuja bien cada ventana. Pero algunos servidores cierran la sesión justo después de entrar; entonces vuelve a GFX."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:267
|
||||||
|
msgid ""
|
||||||
|
"Works with every server. FreeRDP may not repaint a window after it was "
|
||||||
|
"minimized or resized."
|
||||||
|
msgstr "Funciona con cualquier servidor. Puede que FreeRDP no vuelva a dibujar una ventana tras minimizarla o cambiar su tamaño."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:268
|
||||||
|
msgid ""
|
||||||
|
"Always waits before the second sign-in. For when the second prompt does not "
|
||||||
|
"come with automatic."
|
||||||
|
msgstr ""
|
||||||
|
"Espera siempre antes del segundo inicio de sesión. Para cuando con "
|
||||||
|
"automático no llega la segunda solicitud."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:269
|
||||||
|
msgid "Never waits. Right when your company has no MFA."
|
||||||
|
msgstr "Nunca espera. Lo adecuado si tu empresa no usa MFA."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:270
|
||||||
|
msgid ""
|
||||||
|
"Many servers with MFA need a second sign-in, and its prompt only comes after "
|
||||||
|
"a short wait. Automatic waits when the first sign-in took a while."
|
||||||
|
msgstr ""
|
||||||
|
"Muchos servidores con MFA piden un segundo inicio de sesión, y su solicitud "
|
||||||
|
"solo llega tras una breve espera. Automático espera si el primero tardó un "
|
||||||
|
"poco."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:273
|
||||||
|
msgid ""
|
||||||
|
"Loads the list of apps again once a day, so new apps show up by themselves."
|
||||||
|
msgstr ""
|
||||||
|
"Vuelve a cargar la lista de apps una vez al día, así las nuevas aparecen "
|
||||||
|
"solas."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:275 src/lib/menu.sh:330
|
||||||
|
msgid "Needs a systemd user session."
|
||||||
|
msgstr "Necesita una sesión de usuario de systemd."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:322 src/lib/menu.sh:325
|
||||||
|
msgid "Could not save the setting."
|
||||||
|
msgstr "No se pudo guardar el ajuste."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:371 src/lib/menu.sh:609
|
||||||
|
msgid "Settings"
|
||||||
|
msgstr "Ajustes"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:372
|
||||||
|
msgid "↑↓ select ←→ or Space: change q: back"
|
||||||
|
msgstr "↑↓ elegir ←→ o Espacio: cambiar q: volver"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:373
|
||||||
|
msgid "ON"
|
||||||
|
msgstr "ACTIVADO"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:374
|
||||||
|
msgid "OFF"
|
||||||
|
msgstr "DESACTIVADO"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:456 src/lib/menu.sh:608
|
||||||
|
msgid "Workspaces"
|
||||||
|
msgstr "Espacios de trabajo"
|
||||||
|
|
||||||
|
#. TRANSLATORS: keep the letters, they are the keys.
|
||||||
|
#: src/lib/menu.sh:458
|
||||||
|
msgid ""
|
||||||
|
"↑↓ select r: refresh s: sign in again d: remove a: add q: back"
|
||||||
|
msgstr ""
|
||||||
|
"↑↓ elegir r: actualizar s: volver a entrar d: quitar a: añadir q: "
|
||||||
|
"volver"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:459
|
||||||
|
msgid "No workspace yet. Press a to add one."
|
||||||
|
msgstr "Todavía no hay ningún espacio de trabajo. Pulsa a para añadir uno."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:484
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "refreshed %s"
|
||||||
|
msgstr "actualizado %s"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:512
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Remove \"%s\" and its apps?"
|
||||||
|
msgstr "¿Quitar «%s» y sus apps?"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:536 src/lib/menu.sh:607
|
||||||
|
msgid "Start an app"
|
||||||
|
msgstr "Abrir una app"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:537
|
||||||
|
msgid "↑↓ select Enter: start q: back"
|
||||||
|
msgstr "↑↓ elegir Intro: abrir q: volver"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:538
|
||||||
|
msgid "No apps yet. Add a workspace first."
|
||||||
|
msgstr "Todavía no hay apps. Añade primero un espacio de trabajo."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:579
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s is starting. It opens in a window of its own."
|
||||||
|
msgstr "%s se está abriendo. Se abre en una ventana propia."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:606
|
||||||
|
msgid "Add a workspace"
|
||||||
|
msgstr "Añadir un espacio de trabajo"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:610
|
||||||
|
msgid "Quit"
|
||||||
|
msgstr "Salir"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:163
|
||||||
|
msgid "The server did not accept the user name or password."
|
||||||
|
msgstr "El servidor no aceptó el nombre de usuario o la contraseña."
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:164
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not reach the server: %s"
|
||||||
|
msgstr "No se pudo conectar con el servidor: %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:165
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "There is no RD Web feed at %s."
|
||||||
|
msgstr "No hay ningún feed de RD Web en %s."
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:166
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not load the apps: %s"
|
||||||
|
msgstr "No se pudieron cargar las apps: %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:282
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Remote desktop of %s"
|
||||||
|
msgstr "Escritorio remoto de %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:284
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s from %s"
|
||||||
|
msgstr "%s de %s"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Apps"
|
||||||
|
msgstr "Apps"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Share a folder"
|
||||||
|
msgstr "Compartir una carpeta"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Open files with the apps"
|
||||||
|
msgstr "Abrir archivos con las apps"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Size"
|
||||||
|
msgstr "Tamaño"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Sound"
|
||||||
|
msgstr "Sonido"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Graphics"
|
||||||
|
msgstr "Gráficos"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Signing in"
|
||||||
|
msgstr "Inicio de sesión"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Wait for the second MFA prompt"
|
||||||
|
msgstr "Esperar la segunda solicitud de MFA"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Refresh the apps every day"
|
||||||
|
msgstr "Actualizar las apps cada día"
|
||||||
@@ -0,0 +1,574 @@
|
|||||||
|
# French translation for rdfeed.
|
||||||
|
# Copyright (C) 2026 Felitendo
|
||||||
|
# This file is distributed under the same license as rdfeed.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: rdfeed 1.0.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: https://github.com/LoonixTools/rdfeed/issues\n"
|
||||||
|
"POT-Creation-Date: 2026-10-02 10:29+0200\n"
|
||||||
|
"PO-Revision-Date: 2026-10-02 09:30+0200\n"
|
||||||
|
"Last-Translator: Felitendo\n"
|
||||||
|
"Language-Team: French\n"
|
||||||
|
"Language: fr\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: src/rdfeed:79 src/rdfeed:103
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not write to %s."
|
||||||
|
msgstr "Impossible d'écrire dans %s."
|
||||||
|
|
||||||
|
#: src/rdfeed:82
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Signing in to %s…"
|
||||||
|
msgstr "Connexion à %s…"
|
||||||
|
|
||||||
|
#: src/rdfeed:100
|
||||||
|
msgid ""
|
||||||
|
"The password could not be saved in the keyring, so each app asks for it."
|
||||||
|
msgstr ""
|
||||||
|
"Le mot de passe n'a pas pu être enregistré dans le trousseau, chaque "
|
||||||
|
"application le demandera donc."
|
||||||
|
|
||||||
|
#: src/rdfeed:111
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "1 app from %s is now in your app menu."
|
||||||
|
msgstr "1 application de %s est maintenant dans votre menu des applications."
|
||||||
|
|
||||||
|
#: src/rdfeed:113
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d apps from %s are now in your app menu."
|
||||||
|
msgstr ""
|
||||||
|
"%d applications de %s sont maintenant dans votre menu des applications."
|
||||||
|
|
||||||
|
#: src/rdfeed:129
|
||||||
|
msgid ""
|
||||||
|
"Your IT department has the address. A work e-mail address often works too."
|
||||||
|
msgstr ""
|
||||||
|
"Votre service informatique connaît l'adresse. Souvent, votre adresse e-mail "
|
||||||
|
"professionnelle fonctionne aussi."
|
||||||
|
|
||||||
|
#: src/rdfeed:130
|
||||||
|
msgid "Feed address, server or e-mail:"
|
||||||
|
msgstr "Adresse du flux, serveur ou e-mail :"
|
||||||
|
|
||||||
|
#: src/rdfeed:137
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No feed is registered for %s. Ask your IT department for the address."
|
||||||
|
msgstr ""
|
||||||
|
"Aucun flux n'est enregistré pour %s. Demandez l'adresse à votre service "
|
||||||
|
"informatique."
|
||||||
|
|
||||||
|
#: src/rdfeed:142 src/rdfeed:156
|
||||||
|
msgid "User name (DOMAIN\\name or name@domain):"
|
||||||
|
msgstr "Nom d'utilisateur (DOMAINE\\nom ou nom@domaine) :"
|
||||||
|
|
||||||
|
#: src/rdfeed:146 src/rdfeed:159
|
||||||
|
msgid "Password:"
|
||||||
|
msgstr "Mot de passe :"
|
||||||
|
|
||||||
|
#: src/rdfeed:178 src/rdfeed:236
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No workspace yet. Add one with `%s add`."
|
||||||
|
msgstr "Aucun espace de travail pour l'instant. Ajoutez-en un avec `%s add`."
|
||||||
|
|
||||||
|
#: src/rdfeed:187 src/lib/launch.sh:220
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No password saved for %s. Sign in again in rdfeed."
|
||||||
|
msgstr "Aucun mot de passe enregistré pour %s. Reconnectez-vous dans rdfeed."
|
||||||
|
|
||||||
|
#: src/rdfeed:191 src/lib/launch.sh:149
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Password for %s:"
|
||||||
|
msgstr "Mot de passe pour %s :"
|
||||||
|
|
||||||
|
#: src/rdfeed:195
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Refreshing %s…"
|
||||||
|
msgstr "Actualisation de %s…"
|
||||||
|
|
||||||
|
#: src/rdfeed:205
|
||||||
|
msgid "The password no longer works. Sign in again in rdfeed."
|
||||||
|
msgstr "Le mot de passe ne fonctionne plus. Reconnectez-vous dans rdfeed."
|
||||||
|
|
||||||
|
#: src/rdfeed:214
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s: %d apps"
|
||||||
|
msgstr "%s : %d applications"
|
||||||
|
|
||||||
|
#: src/rdfeed:222
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Which workspace? See `%s list` for the names."
|
||||||
|
msgstr "Quel espace de travail ? Les noms sont dans `%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:229
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s and its apps are removed."
|
||||||
|
msgstr "%s et ses applications ont été supprimés."
|
||||||
|
|
||||||
|
#: src/rdfeed:253
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Which app? See `%s list`."
|
||||||
|
msgstr "Quelle application ? Voir `%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:268 src/lib/launch.sh:199
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "There is no app %s. See `%s list`."
|
||||||
|
msgstr "L'application %s n'existe pas. Voir `%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:270
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Several workspaces have %s. Write it as WORKSPACE/APP, see `%s list`."
|
||||||
|
msgstr ""
|
||||||
|
"Plusieurs espaces de travail ont %s. Écrivez-le sous la forme ESPACE/"
|
||||||
|
"APPLICATION, voir `%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:278
|
||||||
|
msgid "Usage: rdfeed [command]"
|
||||||
|
msgstr "Utilisation : rdfeed [commande]"
|
||||||
|
|
||||||
|
#: src/rdfeed:280
|
||||||
|
msgid "Commands:"
|
||||||
|
msgstr "Commandes :"
|
||||||
|
|
||||||
|
#: src/rdfeed:281
|
||||||
|
msgid "Add the workspace of your company"
|
||||||
|
msgstr "Ajouter l'espace de travail de votre entreprise"
|
||||||
|
|
||||||
|
#: src/rdfeed:282
|
||||||
|
msgid "Load the apps again"
|
||||||
|
msgstr "Recharger les applications"
|
||||||
|
|
||||||
|
#: src/rdfeed:283
|
||||||
|
msgid "List the workspaces and their apps"
|
||||||
|
msgstr "Lister les espaces de travail et leurs applications"
|
||||||
|
|
||||||
|
#: src/rdfeed:284
|
||||||
|
msgid "Start an app, or open a file with it"
|
||||||
|
msgstr "Lancer une application, ou ouvrir un fichier avec"
|
||||||
|
|
||||||
|
#: src/rdfeed:285
|
||||||
|
msgid "Remove a workspace and its apps"
|
||||||
|
msgstr "Supprimer un espace de travail et ses applications"
|
||||||
|
|
||||||
|
#: src/rdfeed:286
|
||||||
|
msgid "Show this help"
|
||||||
|
msgstr "Afficher cette aide"
|
||||||
|
|
||||||
|
#: src/rdfeed:287
|
||||||
|
msgid "Show the version"
|
||||||
|
msgstr "Afficher la version"
|
||||||
|
|
||||||
|
#: src/rdfeed:289
|
||||||
|
msgid "Without a command an interactive menu is shown."
|
||||||
|
msgstr "Sans commande, un menu interactif s'affiche."
|
||||||
|
|
||||||
|
#: src/rdfeed:313
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Unknown command: %s"
|
||||||
|
msgstr "Commande inconnue : %s"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:174 src/lib/menu.sh:251
|
||||||
|
msgid "never"
|
||||||
|
msgstr "jamais"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:184
|
||||||
|
msgid "just now"
|
||||||
|
msgstr "à l'instant"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:186
|
||||||
|
msgid "1 minute ago"
|
||||||
|
msgstr "il y a 1 minute"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:189
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d minutes ago"
|
||||||
|
msgstr "il y a %d minutes"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:191
|
||||||
|
msgid "1 hour ago"
|
||||||
|
msgstr "il y a 1 heure"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:194
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d hours ago"
|
||||||
|
msgstr "il y a %d heures"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:196
|
||||||
|
msgid "1 day ago"
|
||||||
|
msgstr "il y a 1 jour"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:199
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d days ago"
|
||||||
|
msgstr "il y a %d jours"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:169
|
||||||
|
msgid ""
|
||||||
|
"Your password has expired. Change it on Windows or on the RD Web page, then "
|
||||||
|
"sign in again in rdfeed."
|
||||||
|
msgstr ""
|
||||||
|
"Votre mot de passe a expiré. Changez-le sous Windows ou sur la page RD Web, "
|
||||||
|
"puis reconnectez-vous dans rdfeed."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:171
|
||||||
|
msgid "Your account is locked. Ask your IT department."
|
||||||
|
msgstr ""
|
||||||
|
"Votre compte est verrouillé. Adressez-vous à votre service informatique."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:173
|
||||||
|
msgid "The server did not accept the password. Sign in again in rdfeed."
|
||||||
|
msgstr ""
|
||||||
|
"Le serveur n'a pas accepté le mot de passe. Reconnectez-vous dans rdfeed."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:175
|
||||||
|
msgid "Could not reach the server. Check the internet connection."
|
||||||
|
msgstr "Impossible de joindre le serveur. Vérifiez la connexion internet."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:178
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "FreeRDP stopped: %s"
|
||||||
|
msgstr "FreeRDP s'est arrêté : %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:205 src/lib/launch.sh:206
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "File not found: %s"
|
||||||
|
msgstr "Fichier introuvable : %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:212 src/lib/launch.sh:213
|
||||||
|
msgid ""
|
||||||
|
"FreeRDP 3 is missing. Install it to start the apps (the package is called "
|
||||||
|
"freerdp)."
|
||||||
|
msgstr ""
|
||||||
|
"FreeRDP 3 est absent. Installez-le pour lancer les applications (le paquet "
|
||||||
|
"s'appelle freerdp)."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:277
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Opening %s in %s"
|
||||||
|
msgstr "Ouverture de %s dans %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:278
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "If it does not open by itself, open it in the app from the drive %s."
|
||||||
|
msgstr ""
|
||||||
|
"S'il ne s'ouvre pas tout seul, ouvrez-le dans l'application depuis le "
|
||||||
|
"lecteur %s."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:280
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Starting %s"
|
||||||
|
msgstr "Lancement de %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:281
|
||||||
|
msgid ""
|
||||||
|
"If your company uses MFA, confirm the sign-in in your authenticator app."
|
||||||
|
msgstr ""
|
||||||
|
"Si votre entreprise utilise la MFA, confirmez la connexion dans votre "
|
||||||
|
"application d'authentification."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:290
|
||||||
|
msgid "A second sign-in is coming"
|
||||||
|
msgstr "Une deuxième connexion arrive"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:291
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid ""
|
||||||
|
"Confirm the second prompt in your authenticator app too. It comes in about "
|
||||||
|
"%d seconds."
|
||||||
|
msgstr ""
|
||||||
|
"Confirmez aussi la deuxième demande dans votre application "
|
||||||
|
"d'authentification. Elle arrive dans environ %d secondes."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:301
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s could not start"
|
||||||
|
msgstr "%s n'a pas pu démarrer"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:303
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Log: %s"
|
||||||
|
msgstr "Journal : %s"
|
||||||
|
|
||||||
|
#. TRANSLATORS: the letter after "[" is the key for yes. y works too.
|
||||||
|
#: src/lib/menu.sh:93
|
||||||
|
msgid "[y/N]"
|
||||||
|
msgstr "[o/N]"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:163
|
||||||
|
msgid "1 app"
|
||||||
|
msgstr "1 application"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:165
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d apps"
|
||||||
|
msgstr "%d applications"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:190
|
||||||
|
msgid "No workspace yet. Add the one of your company with [1]."
|
||||||
|
msgstr ""
|
||||||
|
"Aucun espace de travail pour l'instant. Ajoutez celui de votre entreprise "
|
||||||
|
"avec [1]."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:233
|
||||||
|
msgid "off"
|
||||||
|
msgstr "désactivé"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:234
|
||||||
|
msgid "home folder"
|
||||||
|
msgstr "dossier personnel"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:237
|
||||||
|
msgid "Documents"
|
||||||
|
msgstr "Documents"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:238
|
||||||
|
msgid "Downloads"
|
||||||
|
msgstr "Téléchargements"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:243
|
||||||
|
msgid "not found"
|
||||||
|
msgstr "introuvable"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:246
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "automatic (%d %%)"
|
||||||
|
msgstr "automatique (%d %%)"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:248
|
||||||
|
msgid "classic"
|
||||||
|
msgstr "classique"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:249
|
||||||
|
msgid "GFX"
|
||||||
|
msgstr "GFX"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:250
|
||||||
|
msgid "always"
|
||||||
|
msgstr "toujours"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:252
|
||||||
|
msgid "automatic"
|
||||||
|
msgstr "automatique"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:261
|
||||||
|
msgid ""
|
||||||
|
"The apps only see the files on the server. Pick a folder to open and save "
|
||||||
|
"files of this computer in them."
|
||||||
|
msgstr ""
|
||||||
|
"Les applications ne voient que les fichiers du serveur. Choisissez un "
|
||||||
|
"dossier pour qu'elles puissent ouvrir et enregistrer des fichiers de cet "
|
||||||
|
"ordinateur."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:262
|
||||||
|
msgid ""
|
||||||
|
"The apps can open and save files in this folder. It shows up as a drive in "
|
||||||
|
"them."
|
||||||
|
msgstr ""
|
||||||
|
"Les applications peuvent ouvrir et enregistrer des fichiers dans ce dossier. "
|
||||||
|
"Il y apparaît comme un lecteur."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:263
|
||||||
|
msgid ""
|
||||||
|
"Offers the apps under \"Open with\" for their file types, for example Word "
|
||||||
|
"for .docx. The folder of the file is shared with the app while it is open."
|
||||||
|
msgstr ""
|
||||||
|
"Propose les applications dans « Ouvrir avec » pour leurs types de fichiers, "
|
||||||
|
"par exemple Word pour .docx. Le dossier du fichier est partagé tant qu'il "
|
||||||
|
"est ouvert."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:264
|
||||||
|
msgid ""
|
||||||
|
"How big the apps are drawn. Automatic follows the scale of your desktop, so "
|
||||||
|
"they are sharp and the right size."
|
||||||
|
msgstr ""
|
||||||
|
"La taille à laquelle les applications sont dessinées. Automatique suit "
|
||||||
|
"l'échelle de votre bureau : elles sont nettes et à la bonne taille."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:265
|
||||||
|
msgid "Plays the sound of the apps here, for calls and videos."
|
||||||
|
msgstr "Joue ici le son des applications, pour les appels et les vidéos."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:266
|
||||||
|
msgid ""
|
||||||
|
"Paints every window right. But some servers end the session right after "
|
||||||
|
"signing in, then go back to GFX."
|
||||||
|
msgstr "Dessine correctement chaque fenêtre. Mais certains serveurs ferment la session juste après la connexion : revenez alors à GFX."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:267
|
||||||
|
msgid ""
|
||||||
|
"Works with every server. FreeRDP may not repaint a window after it was "
|
||||||
|
"minimized or resized."
|
||||||
|
msgstr "Fonctionne avec tous les serveurs. FreeRDP peut ne pas redessiner une fenêtre réduite ou redimensionnée."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:268
|
||||||
|
msgid ""
|
||||||
|
"Always waits before the second sign-in. For when the second prompt does not "
|
||||||
|
"come with automatic."
|
||||||
|
msgstr ""
|
||||||
|
"Attend toujours avant la deuxième connexion. Si la deuxième demande n'arrive "
|
||||||
|
"pas en automatique."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:269
|
||||||
|
msgid "Never waits. Right when your company has no MFA."
|
||||||
|
msgstr ""
|
||||||
|
"N'attend jamais. Le bon choix si votre entreprise n'utilise pas la MFA."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:270
|
||||||
|
msgid ""
|
||||||
|
"Many servers with MFA need a second sign-in, and its prompt only comes after "
|
||||||
|
"a short wait. Automatic waits when the first sign-in took a while."
|
||||||
|
msgstr ""
|
||||||
|
"Beaucoup de serveurs avec MFA demandent une deuxième connexion, dont la "
|
||||||
|
"demande n'arrive qu'après une courte attente. Automatique attend si la "
|
||||||
|
"première a pris du temps."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:273
|
||||||
|
msgid ""
|
||||||
|
"Loads the list of apps again once a day, so new apps show up by themselves."
|
||||||
|
msgstr ""
|
||||||
|
"Recharge la liste des applications une fois par jour : les nouvelles "
|
||||||
|
"apparaissent d'elles-mêmes."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:275 src/lib/menu.sh:330
|
||||||
|
msgid "Needs a systemd user session."
|
||||||
|
msgstr "Nécessite une session utilisateur systemd."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:322 src/lib/menu.sh:325
|
||||||
|
msgid "Could not save the setting."
|
||||||
|
msgstr "Impossible d'enregistrer le réglage."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:371 src/lib/menu.sh:609
|
||||||
|
msgid "Settings"
|
||||||
|
msgstr "Réglages"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:372
|
||||||
|
msgid "↑↓ select ←→ or Space: change q: back"
|
||||||
|
msgstr "↑↓ choisir ←→ ou Espace : changer q : retour"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:373
|
||||||
|
msgid "ON"
|
||||||
|
msgstr "ACTIVÉ"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:374
|
||||||
|
msgid "OFF"
|
||||||
|
msgstr "DÉSACTIVÉ"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:456 src/lib/menu.sh:608
|
||||||
|
msgid "Workspaces"
|
||||||
|
msgstr "Espaces de travail"
|
||||||
|
|
||||||
|
#. TRANSLATORS: keep the letters, they are the keys.
|
||||||
|
#: src/lib/menu.sh:458
|
||||||
|
msgid ""
|
||||||
|
"↑↓ select r: refresh s: sign in again d: remove a: add q: back"
|
||||||
|
msgstr ""
|
||||||
|
"↑↓ choisir r : actualiser s : se reconnecter d : supprimer a : "
|
||||||
|
"ajouter q : retour"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:459
|
||||||
|
msgid "No workspace yet. Press a to add one."
|
||||||
|
msgstr ""
|
||||||
|
"Aucun espace de travail pour l'instant. Appuyez sur a pour en ajouter un."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:484
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "refreshed %s"
|
||||||
|
msgstr "actualisé %s"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:512
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Remove \"%s\" and its apps?"
|
||||||
|
msgstr "Supprimer « %s » et ses applications ?"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:536 src/lib/menu.sh:607
|
||||||
|
msgid "Start an app"
|
||||||
|
msgstr "Lancer une application"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:537
|
||||||
|
msgid "↑↓ select Enter: start q: back"
|
||||||
|
msgstr "↑↓ choisir Entrée : lancer q : retour"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:538
|
||||||
|
msgid "No apps yet. Add a workspace first."
|
||||||
|
msgstr "Pas encore d'applications. Ajoutez d'abord un espace de travail."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:579
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s is starting. It opens in a window of its own."
|
||||||
|
msgstr "%s démarre. L'application s'ouvre dans sa propre fenêtre."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:606
|
||||||
|
msgid "Add a workspace"
|
||||||
|
msgstr "Ajouter un espace de travail"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:610
|
||||||
|
msgid "Quit"
|
||||||
|
msgstr "Quitter"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:163
|
||||||
|
msgid "The server did not accept the user name or password."
|
||||||
|
msgstr "Le serveur n'a pas accepté le nom d'utilisateur ou le mot de passe."
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:164
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not reach the server: %s"
|
||||||
|
msgstr "Impossible de joindre le serveur : %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:165
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "There is no RD Web feed at %s."
|
||||||
|
msgstr "Il n'y a pas de flux RD Web à l'adresse %s."
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:166
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not load the apps: %s"
|
||||||
|
msgstr "Impossible de charger les applications : %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:282
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Remote desktop of %s"
|
||||||
|
msgstr "Bureau à distance de %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:284
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s from %s"
|
||||||
|
msgstr "%s de %s"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Apps"
|
||||||
|
msgstr "Applications"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Share a folder"
|
||||||
|
msgstr "Partager un dossier"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Open files with the apps"
|
||||||
|
msgstr "Ouvrir des fichiers avec les applications"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Size"
|
||||||
|
msgstr "Taille"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Sound"
|
||||||
|
msgstr "Son"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Graphics"
|
||||||
|
msgstr "Graphismes"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Signing in"
|
||||||
|
msgstr "Connexion"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Wait for the second MFA prompt"
|
||||||
|
msgstr "Attendre la deuxième demande MFA"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Refresh the apps every day"
|
||||||
|
msgstr "Actualiser les applications chaque jour"
|
||||||
@@ -0,0 +1,561 @@
|
|||||||
|
# Italian translation for rdfeed.
|
||||||
|
# Copyright (C) 2026 Felitendo
|
||||||
|
# This file is distributed under the same license as rdfeed.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: rdfeed 1.0.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: https://github.com/LoonixTools/rdfeed/issues\n"
|
||||||
|
"POT-Creation-Date: 2026-10-02 10:29+0200\n"
|
||||||
|
"PO-Revision-Date: 2026-10-02 09:30+0200\n"
|
||||||
|
"Last-Translator: Felitendo\n"
|
||||||
|
"Language-Team: Italian\n"
|
||||||
|
"Language: it\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: src/rdfeed:79 src/rdfeed:103
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not write to %s."
|
||||||
|
msgstr "Impossibile scrivere in %s."
|
||||||
|
|
||||||
|
#: src/rdfeed:82
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Signing in to %s…"
|
||||||
|
msgstr "Accesso a %s…"
|
||||||
|
|
||||||
|
#: src/rdfeed:100
|
||||||
|
msgid ""
|
||||||
|
"The password could not be saved in the keyring, so each app asks for it."
|
||||||
|
msgstr ""
|
||||||
|
"Non è stato possibile salvare la password nel portachiavi, quindi ogni app "
|
||||||
|
"la chiederà."
|
||||||
|
|
||||||
|
#: src/rdfeed:111
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "1 app from %s is now in your app menu."
|
||||||
|
msgstr "1 app di %s è ora nel menu delle applicazioni."
|
||||||
|
|
||||||
|
#: src/rdfeed:113
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d apps from %s are now in your app menu."
|
||||||
|
msgstr "%d app di %s sono ora nel menu delle applicazioni."
|
||||||
|
|
||||||
|
#: src/rdfeed:129
|
||||||
|
msgid ""
|
||||||
|
"Your IT department has the address. A work e-mail address often works too."
|
||||||
|
msgstr ""
|
||||||
|
"L'indirizzo lo ha il tuo reparto IT. Spesso funziona anche l'e-mail di "
|
||||||
|
"lavoro."
|
||||||
|
|
||||||
|
#: src/rdfeed:130
|
||||||
|
msgid "Feed address, server or e-mail:"
|
||||||
|
msgstr "Indirizzo del feed, server o e-mail:"
|
||||||
|
|
||||||
|
#: src/rdfeed:137
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No feed is registered for %s. Ask your IT department for the address."
|
||||||
|
msgstr "Nessun feed registrato per %s. Chiedi l'indirizzo al tuo reparto IT."
|
||||||
|
|
||||||
|
#: src/rdfeed:142 src/rdfeed:156
|
||||||
|
msgid "User name (DOMAIN\\name or name@domain):"
|
||||||
|
msgstr "Nome utente (DOMINIO\\nome o nome@dominio):"
|
||||||
|
|
||||||
|
#: src/rdfeed:146 src/rdfeed:159
|
||||||
|
msgid "Password:"
|
||||||
|
msgstr "Password:"
|
||||||
|
|
||||||
|
#: src/rdfeed:178 src/rdfeed:236
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No workspace yet. Add one with `%s add`."
|
||||||
|
msgstr "Nessuno spazio di lavoro ancora. Aggiungine uno con `%s add`."
|
||||||
|
|
||||||
|
#: src/rdfeed:187 src/lib/launch.sh:220
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No password saved for %s. Sign in again in rdfeed."
|
||||||
|
msgstr "Nessuna password salvata per %s. Accedi di nuovo in rdfeed."
|
||||||
|
|
||||||
|
#: src/rdfeed:191 src/lib/launch.sh:149
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Password for %s:"
|
||||||
|
msgstr "Password per %s:"
|
||||||
|
|
||||||
|
#: src/rdfeed:195
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Refreshing %s…"
|
||||||
|
msgstr "Aggiornamento di %s…"
|
||||||
|
|
||||||
|
#: src/rdfeed:205
|
||||||
|
msgid "The password no longer works. Sign in again in rdfeed."
|
||||||
|
msgstr "La password non funziona più. Accedi di nuovo in rdfeed."
|
||||||
|
|
||||||
|
#: src/rdfeed:214
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s: %d apps"
|
||||||
|
msgstr "%s: %d app"
|
||||||
|
|
||||||
|
#: src/rdfeed:222
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Which workspace? See `%s list` for the names."
|
||||||
|
msgstr "Quale spazio di lavoro? I nomi sono in `%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:229
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s and its apps are removed."
|
||||||
|
msgstr "%s e le sue app sono stati rimossi."
|
||||||
|
|
||||||
|
#: src/rdfeed:253
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Which app? See `%s list`."
|
||||||
|
msgstr "Quale app? Vedi `%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:268 src/lib/launch.sh:199
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "There is no app %s. See `%s list`."
|
||||||
|
msgstr "Non esiste l'app %s. Vedi `%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:270
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Several workspaces have %s. Write it as WORKSPACE/APP, see `%s list`."
|
||||||
|
msgstr ""
|
||||||
|
"Più spazi di lavoro hanno %s. Scrivilo come SPAZIO/APP, vedi `%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:278
|
||||||
|
msgid "Usage: rdfeed [command]"
|
||||||
|
msgstr "Uso: rdfeed [comando]"
|
||||||
|
|
||||||
|
#: src/rdfeed:280
|
||||||
|
msgid "Commands:"
|
||||||
|
msgstr "Comandi:"
|
||||||
|
|
||||||
|
#: src/rdfeed:281
|
||||||
|
msgid "Add the workspace of your company"
|
||||||
|
msgstr "Aggiungi lo spazio di lavoro della tua azienda"
|
||||||
|
|
||||||
|
#: src/rdfeed:282
|
||||||
|
msgid "Load the apps again"
|
||||||
|
msgstr "Ricarica le app"
|
||||||
|
|
||||||
|
#: src/rdfeed:283
|
||||||
|
msgid "List the workspaces and their apps"
|
||||||
|
msgstr "Elenca gli spazi di lavoro e le loro app"
|
||||||
|
|
||||||
|
#: src/rdfeed:284
|
||||||
|
msgid "Start an app, or open a file with it"
|
||||||
|
msgstr "Avvia un'app, o apri un file con essa"
|
||||||
|
|
||||||
|
#: src/rdfeed:285
|
||||||
|
msgid "Remove a workspace and its apps"
|
||||||
|
msgstr "Rimuovi uno spazio di lavoro e le sue app"
|
||||||
|
|
||||||
|
#: src/rdfeed:286
|
||||||
|
msgid "Show this help"
|
||||||
|
msgstr "Mostra questo aiuto"
|
||||||
|
|
||||||
|
#: src/rdfeed:287
|
||||||
|
msgid "Show the version"
|
||||||
|
msgstr "Mostra la versione"
|
||||||
|
|
||||||
|
#: src/rdfeed:289
|
||||||
|
msgid "Without a command an interactive menu is shown."
|
||||||
|
msgstr "Senza comando viene mostrato un menu interattivo."
|
||||||
|
|
||||||
|
#: src/rdfeed:313
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Unknown command: %s"
|
||||||
|
msgstr "Comando sconosciuto: %s"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:174 src/lib/menu.sh:251
|
||||||
|
msgid "never"
|
||||||
|
msgstr "mai"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:184
|
||||||
|
msgid "just now"
|
||||||
|
msgstr "proprio ora"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:186
|
||||||
|
msgid "1 minute ago"
|
||||||
|
msgstr "1 minuto fa"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:189
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d minutes ago"
|
||||||
|
msgstr "%d minuti fa"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:191
|
||||||
|
msgid "1 hour ago"
|
||||||
|
msgstr "1 ora fa"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:194
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d hours ago"
|
||||||
|
msgstr "%d ore fa"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:196
|
||||||
|
msgid "1 day ago"
|
||||||
|
msgstr "1 giorno fa"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:199
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d days ago"
|
||||||
|
msgstr "%d giorni fa"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:169
|
||||||
|
msgid ""
|
||||||
|
"Your password has expired. Change it on Windows or on the RD Web page, then "
|
||||||
|
"sign in again in rdfeed."
|
||||||
|
msgstr ""
|
||||||
|
"La password è scaduta. Cambiala in Windows o sulla pagina RD Web, poi accedi "
|
||||||
|
"di nuovo in rdfeed."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:171
|
||||||
|
msgid "Your account is locked. Ask your IT department."
|
||||||
|
msgstr "Il tuo account è bloccato. Chiedi al reparto IT."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:173
|
||||||
|
msgid "The server did not accept the password. Sign in again in rdfeed."
|
||||||
|
msgstr "Il server non ha accettato la password. Accedi di nuovo in rdfeed."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:175
|
||||||
|
msgid "Could not reach the server. Check the internet connection."
|
||||||
|
msgstr ""
|
||||||
|
"Impossibile raggiungere il server. Controlla la connessione a internet."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:178
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "FreeRDP stopped: %s"
|
||||||
|
msgstr "FreeRDP si è fermato: %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:205 src/lib/launch.sh:206
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "File not found: %s"
|
||||||
|
msgstr "File non trovato: %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:212 src/lib/launch.sh:213
|
||||||
|
msgid ""
|
||||||
|
"FreeRDP 3 is missing. Install it to start the apps (the package is called "
|
||||||
|
"freerdp)."
|
||||||
|
msgstr ""
|
||||||
|
"Manca FreeRDP 3. Installalo per avviare le app (il pacchetto si chiama "
|
||||||
|
"freerdp)."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:277
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Opening %s in %s"
|
||||||
|
msgstr "Apertura di %s in %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:278
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "If it does not open by itself, open it in the app from the drive %s."
|
||||||
|
msgstr "Se non si apre da solo, aprilo nell'app dall'unità %s."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:280
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Starting %s"
|
||||||
|
msgstr "Avvio di %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:281
|
||||||
|
msgid ""
|
||||||
|
"If your company uses MFA, confirm the sign-in in your authenticator app."
|
||||||
|
msgstr ""
|
||||||
|
"Se la tua azienda usa l'MFA, conferma l'accesso nell'app di autenticazione."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:290
|
||||||
|
msgid "A second sign-in is coming"
|
||||||
|
msgstr "Sta arrivando un secondo accesso"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:291
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid ""
|
||||||
|
"Confirm the second prompt in your authenticator app too. It comes in about "
|
||||||
|
"%d seconds."
|
||||||
|
msgstr ""
|
||||||
|
"Conferma anche la seconda richiesta nell'app di autenticazione. Arriva tra "
|
||||||
|
"circa %d secondi."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:301
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s could not start"
|
||||||
|
msgstr "Impossibile avviare %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:303
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Log: %s"
|
||||||
|
msgstr "Log: %s"
|
||||||
|
|
||||||
|
#. TRANSLATORS: the letter after "[" is the key for yes. y works too.
|
||||||
|
#: src/lib/menu.sh:93
|
||||||
|
msgid "[y/N]"
|
||||||
|
msgstr "[s/N]"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:163
|
||||||
|
msgid "1 app"
|
||||||
|
msgstr "1 app"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:165
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d apps"
|
||||||
|
msgstr "%d app"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:190
|
||||||
|
msgid "No workspace yet. Add the one of your company with [1]."
|
||||||
|
msgstr ""
|
||||||
|
"Nessuno spazio di lavoro ancora. Aggiungi quello della tua azienda con [1]."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:233
|
||||||
|
msgid "off"
|
||||||
|
msgstr "disattivo"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:234
|
||||||
|
msgid "home folder"
|
||||||
|
msgstr "cartella home"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:237
|
||||||
|
msgid "Documents"
|
||||||
|
msgstr "Documenti"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:238
|
||||||
|
msgid "Downloads"
|
||||||
|
msgstr "Download"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:243
|
||||||
|
msgid "not found"
|
||||||
|
msgstr "non trovata"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:246
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "automatic (%d %%)"
|
||||||
|
msgstr "automatica (%d %%)"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:248
|
||||||
|
msgid "classic"
|
||||||
|
msgstr "classico"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:249
|
||||||
|
msgid "GFX"
|
||||||
|
msgstr "GFX"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:250
|
||||||
|
msgid "always"
|
||||||
|
msgstr "sempre"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:252
|
||||||
|
msgid "automatic"
|
||||||
|
msgstr "automatico"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:261
|
||||||
|
msgid ""
|
||||||
|
"The apps only see the files on the server. Pick a folder to open and save "
|
||||||
|
"files of this computer in them."
|
||||||
|
msgstr ""
|
||||||
|
"Le app vedono solo i file sul server. Scegli una cartella perché possano "
|
||||||
|
"aprire e salvare file di questo computer."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:262
|
||||||
|
msgid ""
|
||||||
|
"The apps can open and save files in this folder. It shows up as a drive in "
|
||||||
|
"them."
|
||||||
|
msgstr ""
|
||||||
|
"Le app possono aprire e salvare file in questa cartella. Compare in esse "
|
||||||
|
"come un'unità."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:263
|
||||||
|
msgid ""
|
||||||
|
"Offers the apps under \"Open with\" for their file types, for example Word "
|
||||||
|
"for .docx. The folder of the file is shared with the app while it is open."
|
||||||
|
msgstr ""
|
||||||
|
"Offre le app in «Apri con» per i loro tipi di file, per esempio Word "
|
||||||
|
"per .docx. La cartella del file viene condivisa finché è aperto."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:264
|
||||||
|
msgid ""
|
||||||
|
"How big the apps are drawn. Automatic follows the scale of your desktop, so "
|
||||||
|
"they are sharp and the right size."
|
||||||
|
msgstr ""
|
||||||
|
"Quanto grandi vengono disegnate le app. Automatica segue la scala del "
|
||||||
|
"desktop, così sono nitide e della giusta dimensione."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:265
|
||||||
|
msgid "Plays the sound of the apps here, for calls and videos."
|
||||||
|
msgstr "Riproduce qui l'audio delle app, per chiamate e video."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:266
|
||||||
|
msgid ""
|
||||||
|
"Paints every window right. But some servers end the session right after "
|
||||||
|
"signing in, then go back to GFX."
|
||||||
|
msgstr "Disegna bene ogni finestra. Ma alcuni server chiudono la sessione subito dopo l'accesso: allora torna a GFX."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:267
|
||||||
|
msgid ""
|
||||||
|
"Works with every server. FreeRDP may not repaint a window after it was "
|
||||||
|
"minimized or resized."
|
||||||
|
msgstr "Funziona con ogni server. FreeRDP potrebbe non ridisegnare una finestra dopo averla ridotta o ridimensionata."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:268
|
||||||
|
msgid ""
|
||||||
|
"Always waits before the second sign-in. For when the second prompt does not "
|
||||||
|
"come with automatic."
|
||||||
|
msgstr ""
|
||||||
|
"Attende sempre prima del secondo accesso. Per quando con automatico la "
|
||||||
|
"seconda richiesta non arriva."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:269
|
||||||
|
msgid "Never waits. Right when your company has no MFA."
|
||||||
|
msgstr "Non attende mai. Giusto se la tua azienda non usa l'MFA."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:270
|
||||||
|
msgid ""
|
||||||
|
"Many servers with MFA need a second sign-in, and its prompt only comes after "
|
||||||
|
"a short wait. Automatic waits when the first sign-in took a while."
|
||||||
|
msgstr ""
|
||||||
|
"Molti server con MFA chiedono un secondo accesso, e la sua richiesta arriva "
|
||||||
|
"solo dopo una breve attesa. Automatico attende se il primo accesso ha "
|
||||||
|
"richiesto un po'."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:273
|
||||||
|
msgid ""
|
||||||
|
"Loads the list of apps again once a day, so new apps show up by themselves."
|
||||||
|
msgstr ""
|
||||||
|
"Ricarica l'elenco delle app una volta al giorno, così quelle nuove compaiono "
|
||||||
|
"da sole."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:275 src/lib/menu.sh:330
|
||||||
|
msgid "Needs a systemd user session."
|
||||||
|
msgstr "Serve una sessione utente di systemd."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:322 src/lib/menu.sh:325
|
||||||
|
msgid "Could not save the setting."
|
||||||
|
msgstr "Impossibile salvare l'impostazione."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:371 src/lib/menu.sh:609
|
||||||
|
msgid "Settings"
|
||||||
|
msgstr "Impostazioni"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:372
|
||||||
|
msgid "↑↓ select ←→ or Space: change q: back"
|
||||||
|
msgstr "↑↓ scegli ←→ o Spazio: cambia q: indietro"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:373
|
||||||
|
msgid "ON"
|
||||||
|
msgstr "ATTIVO"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:374
|
||||||
|
msgid "OFF"
|
||||||
|
msgstr "DISATTIVO"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:456 src/lib/menu.sh:608
|
||||||
|
msgid "Workspaces"
|
||||||
|
msgstr "Spazi di lavoro"
|
||||||
|
|
||||||
|
#. TRANSLATORS: keep the letters, they are the keys.
|
||||||
|
#: src/lib/menu.sh:458
|
||||||
|
msgid ""
|
||||||
|
"↑↓ select r: refresh s: sign in again d: remove a: add q: back"
|
||||||
|
msgstr ""
|
||||||
|
"↑↓ scegli r: aggiorna s: accedi di nuovo d: rimuovi a: aggiungi q: "
|
||||||
|
"indietro"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:459
|
||||||
|
msgid "No workspace yet. Press a to add one."
|
||||||
|
msgstr "Nessuno spazio di lavoro ancora. Premi a per aggiungerne uno."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:484
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "refreshed %s"
|
||||||
|
msgstr "aggiornato %s"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:512
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Remove \"%s\" and its apps?"
|
||||||
|
msgstr "Rimuovere «%s» e le sue app?"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:536 src/lib/menu.sh:607
|
||||||
|
msgid "Start an app"
|
||||||
|
msgstr "Avvia un'app"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:537
|
||||||
|
msgid "↑↓ select Enter: start q: back"
|
||||||
|
msgstr "↑↓ scegli Invio: avvia q: indietro"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:538
|
||||||
|
msgid "No apps yet. Add a workspace first."
|
||||||
|
msgstr "Nessuna app ancora. Aggiungi prima uno spazio di lavoro."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:579
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s is starting. It opens in a window of its own."
|
||||||
|
msgstr "%s si sta avviando. Si apre in una finestra propria."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:606
|
||||||
|
msgid "Add a workspace"
|
||||||
|
msgstr "Aggiungi uno spazio di lavoro"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:610
|
||||||
|
msgid "Quit"
|
||||||
|
msgstr "Esci"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:163
|
||||||
|
msgid "The server did not accept the user name or password."
|
||||||
|
msgstr "Il server non ha accettato il nome utente o la password."
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:164
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not reach the server: %s"
|
||||||
|
msgstr "Impossibile raggiungere il server: %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:165
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "There is no RD Web feed at %s."
|
||||||
|
msgstr "Non c'è nessun feed RD Web in %s."
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:166
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not load the apps: %s"
|
||||||
|
msgstr "Impossibile caricare le app: %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:282
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Remote desktop of %s"
|
||||||
|
msgstr "Desktop remoto di %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:284
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s from %s"
|
||||||
|
msgstr "%s di %s"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Apps"
|
||||||
|
msgstr "App"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Share a folder"
|
||||||
|
msgstr "Condividi una cartella"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Open files with the apps"
|
||||||
|
msgstr "Apri i file con le app"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Size"
|
||||||
|
msgstr "Dimensione"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Sound"
|
||||||
|
msgstr "Audio"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Graphics"
|
||||||
|
msgstr "Grafica"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Signing in"
|
||||||
|
msgstr "Accesso"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Wait for the second MFA prompt"
|
||||||
|
msgstr "Attendi la seconda richiesta MFA"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Refresh the apps every day"
|
||||||
|
msgstr "Aggiorna le app ogni giorno"
|
||||||
@@ -0,0 +1,561 @@
|
|||||||
|
# Japanese translation for rdfeed.
|
||||||
|
# Copyright (C) 2026 Felitendo
|
||||||
|
# This file is distributed under the same license as rdfeed.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: rdfeed 1.0.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: https://github.com/LoonixTools/rdfeed/issues\n"
|
||||||
|
"POT-Creation-Date: 2026-10-02 10:29+0200\n"
|
||||||
|
"PO-Revision-Date: 2026-10-02 09:30+0200\n"
|
||||||
|
"Last-Translator: Felitendo\n"
|
||||||
|
"Language-Team: Japanese\n"
|
||||||
|
"Language: ja\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: src/rdfeed:79 src/rdfeed:103
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not write to %s."
|
||||||
|
msgstr "%s に書き込めませんでした。"
|
||||||
|
|
||||||
|
#: src/rdfeed:82
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Signing in to %s…"
|
||||||
|
msgstr "%s にサインインしています…"
|
||||||
|
|
||||||
|
#: src/rdfeed:100
|
||||||
|
msgid ""
|
||||||
|
"The password could not be saved in the keyring, so each app asks for it."
|
||||||
|
msgstr ""
|
||||||
|
"パスワードをキーリングに保存できなかったため、アプリごとに入力を求めます。"
|
||||||
|
|
||||||
|
#: src/rdfeed:111
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "1 app from %s is now in your app menu."
|
||||||
|
msgstr "%s のアプリ 1 個がアプリメニューに追加されました。"
|
||||||
|
|
||||||
|
#: src/rdfeed:113
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d apps from %s are now in your app menu."
|
||||||
|
msgstr "%d 個のアプリ(%s)がアプリメニューに追加されました。"
|
||||||
|
|
||||||
|
#: src/rdfeed:129
|
||||||
|
msgid ""
|
||||||
|
"Your IT department has the address. A work e-mail address often works too."
|
||||||
|
msgstr ""
|
||||||
|
"アドレスは IT 部門が知っています。仕事用のメールアドレスでも使えることがよく"
|
||||||
|
"あります。"
|
||||||
|
|
||||||
|
#: src/rdfeed:130
|
||||||
|
msgid "Feed address, server or e-mail:"
|
||||||
|
msgstr "フィードのアドレス、サーバー、またはメール:"
|
||||||
|
|
||||||
|
#: src/rdfeed:137
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No feed is registered for %s. Ask your IT department for the address."
|
||||||
|
msgstr ""
|
||||||
|
"%s にはフィードが登録されていません。IT 部門にアドレスを確認してください。"
|
||||||
|
|
||||||
|
#: src/rdfeed:142 src/rdfeed:156
|
||||||
|
msgid "User name (DOMAIN\\name or name@domain):"
|
||||||
|
msgstr "ユーザー名(DOMAIN\\name または name@domain):"
|
||||||
|
|
||||||
|
#: src/rdfeed:146 src/rdfeed:159
|
||||||
|
msgid "Password:"
|
||||||
|
msgstr "パスワード:"
|
||||||
|
|
||||||
|
#: src/rdfeed:178 src/rdfeed:236
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No workspace yet. Add one with `%s add`."
|
||||||
|
msgstr "ワークスペースはまだありません。`%s add` で追加してください。"
|
||||||
|
|
||||||
|
#: src/rdfeed:187 src/lib/launch.sh:220
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No password saved for %s. Sign in again in rdfeed."
|
||||||
|
msgstr ""
|
||||||
|
"%s のパスワードが保存されていません。rdfeed でもう一度サインインしてくださ"
|
||||||
|
"い。"
|
||||||
|
|
||||||
|
#: src/rdfeed:191 src/lib/launch.sh:149
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Password for %s:"
|
||||||
|
msgstr "%s のパスワード:"
|
||||||
|
|
||||||
|
#: src/rdfeed:195
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Refreshing %s…"
|
||||||
|
msgstr "%s を更新しています…"
|
||||||
|
|
||||||
|
#: src/rdfeed:205
|
||||||
|
msgid "The password no longer works. Sign in again in rdfeed."
|
||||||
|
msgstr ""
|
||||||
|
"パスワードが使えなくなりました。rdfeed でもう一度サインインしてください。"
|
||||||
|
|
||||||
|
#: src/rdfeed:214
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s: %d apps"
|
||||||
|
msgstr "%s: アプリ %d 個"
|
||||||
|
|
||||||
|
#: src/rdfeed:222
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Which workspace? See `%s list` for the names."
|
||||||
|
msgstr "どのワークスペースですか?名前は `%s list` で確認できます。"
|
||||||
|
|
||||||
|
#: src/rdfeed:229
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s and its apps are removed."
|
||||||
|
msgstr "%s とそのアプリを削除しました。"
|
||||||
|
|
||||||
|
#: src/rdfeed:253
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Which app? See `%s list`."
|
||||||
|
msgstr "どのアプリですか?`%s list` を参照してください。"
|
||||||
|
|
||||||
|
#: src/rdfeed:268 src/lib/launch.sh:199
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "There is no app %s. See `%s list`."
|
||||||
|
msgstr "アプリ %s はありません。`%s list` を参照してください。"
|
||||||
|
|
||||||
|
#: src/rdfeed:270
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Several workspaces have %s. Write it as WORKSPACE/APP, see `%s list`."
|
||||||
|
msgstr ""
|
||||||
|
"複数のワークスペースに %s があります。ワークスペース/アプリ の形で指定してく"
|
||||||
|
"ださい(`%s list` を参照)。"
|
||||||
|
|
||||||
|
#: src/rdfeed:278
|
||||||
|
msgid "Usage: rdfeed [command]"
|
||||||
|
msgstr "使い方: rdfeed [コマンド]"
|
||||||
|
|
||||||
|
#: src/rdfeed:280
|
||||||
|
msgid "Commands:"
|
||||||
|
msgstr "コマンド:"
|
||||||
|
|
||||||
|
#: src/rdfeed:281
|
||||||
|
msgid "Add the workspace of your company"
|
||||||
|
msgstr "会社のワークスペースを追加する"
|
||||||
|
|
||||||
|
#: src/rdfeed:282
|
||||||
|
msgid "Load the apps again"
|
||||||
|
msgstr "アプリを読み込み直す"
|
||||||
|
|
||||||
|
#: src/rdfeed:283
|
||||||
|
msgid "List the workspaces and their apps"
|
||||||
|
msgstr "ワークスペースとそのアプリを一覧表示する"
|
||||||
|
|
||||||
|
#: src/rdfeed:284
|
||||||
|
msgid "Start an app, or open a file with it"
|
||||||
|
msgstr "アプリを起動する、またはファイルを開く"
|
||||||
|
|
||||||
|
#: src/rdfeed:285
|
||||||
|
msgid "Remove a workspace and its apps"
|
||||||
|
msgstr "ワークスペースとそのアプリを削除する"
|
||||||
|
|
||||||
|
#: src/rdfeed:286
|
||||||
|
msgid "Show this help"
|
||||||
|
msgstr "このヘルプを表示"
|
||||||
|
|
||||||
|
#: src/rdfeed:287
|
||||||
|
msgid "Show the version"
|
||||||
|
msgstr "バージョンを表示"
|
||||||
|
|
||||||
|
#: src/rdfeed:289
|
||||||
|
msgid "Without a command an interactive menu is shown."
|
||||||
|
msgstr "コマンドなしで起動すると、対話式メニューが表示されます。"
|
||||||
|
|
||||||
|
#: src/rdfeed:313
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Unknown command: %s"
|
||||||
|
msgstr "不明なコマンド: %s"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:174 src/lib/menu.sh:251
|
||||||
|
msgid "never"
|
||||||
|
msgstr "なし"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:184
|
||||||
|
msgid "just now"
|
||||||
|
msgstr "たった今"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:186
|
||||||
|
msgid "1 minute ago"
|
||||||
|
msgstr "1 分前"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:189
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d minutes ago"
|
||||||
|
msgstr "%d 分前"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:191
|
||||||
|
msgid "1 hour ago"
|
||||||
|
msgstr "1 時間前"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:194
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d hours ago"
|
||||||
|
msgstr "%d 時間前"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:196
|
||||||
|
msgid "1 day ago"
|
||||||
|
msgstr "1 日前"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:199
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d days ago"
|
||||||
|
msgstr "%d 日前"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:169
|
||||||
|
msgid ""
|
||||||
|
"Your password has expired. Change it on Windows or on the RD Web page, then "
|
||||||
|
"sign in again in rdfeed."
|
||||||
|
msgstr ""
|
||||||
|
"パスワードの有効期限が切れています。Windows か RD Web のページで変更してか"
|
||||||
|
"ら、rdfeed でもう一度サインインしてください。"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:171
|
||||||
|
msgid "Your account is locked. Ask your IT department."
|
||||||
|
msgstr "アカウントがロックされています。IT 部門に問い合わせてください。"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:173
|
||||||
|
msgid "The server did not accept the password. Sign in again in rdfeed."
|
||||||
|
msgstr ""
|
||||||
|
"サーバーがパスワードを受け付けませんでした。rdfeed でもう一度サインインしてく"
|
||||||
|
"ださい。"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:175
|
||||||
|
msgid "Could not reach the server. Check the internet connection."
|
||||||
|
msgstr "サーバーに接続できませんでした。インターネット接続を確認してください。"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:178
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "FreeRDP stopped: %s"
|
||||||
|
msgstr "FreeRDP が停止しました: %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:205 src/lib/launch.sh:206
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "File not found: %s"
|
||||||
|
msgstr "ファイルが見つかりません: %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:212 src/lib/launch.sh:213
|
||||||
|
msgid ""
|
||||||
|
"FreeRDP 3 is missing. Install it to start the apps (the package is called "
|
||||||
|
"freerdp)."
|
||||||
|
msgstr ""
|
||||||
|
"FreeRDP 3 がありません。アプリを起動するにはインストールしてください(パッ"
|
||||||
|
"ケージ名は freerdp)。"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:277
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Opening %s in %s"
|
||||||
|
msgstr "%s を %s で開いています"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:278
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "If it does not open by itself, open it in the app from the drive %s."
|
||||||
|
msgstr "自動で開かない場合は、アプリでドライブ %s から開いてください。"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:280
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Starting %s"
|
||||||
|
msgstr "%s を起動しています"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:281
|
||||||
|
msgid ""
|
||||||
|
"If your company uses MFA, confirm the sign-in in your authenticator app."
|
||||||
|
msgstr ""
|
||||||
|
"会社が MFA を使っている場合は、認証アプリでサインインを承認してください。"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:290
|
||||||
|
msgid "A second sign-in is coming"
|
||||||
|
msgstr "2 回目のサインインが来ます"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:291
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid ""
|
||||||
|
"Confirm the second prompt in your authenticator app too. It comes in about "
|
||||||
|
"%d seconds."
|
||||||
|
msgstr "認証アプリで 2 回目の確認も承認してください。約 %d 秒後に届きます。"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:301
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s could not start"
|
||||||
|
msgstr "%s を起動できませんでした"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:303
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Log: %s"
|
||||||
|
msgstr "ログ: %s"
|
||||||
|
|
||||||
|
#. TRANSLATORS: the letter after "[" is the key for yes. y works too.
|
||||||
|
#: src/lib/menu.sh:93
|
||||||
|
msgid "[y/N]"
|
||||||
|
msgstr "[y/N]"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:163
|
||||||
|
msgid "1 app"
|
||||||
|
msgstr "アプリ 1 個"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:165
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d apps"
|
||||||
|
msgstr "アプリ %d 個"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:190
|
||||||
|
msgid "No workspace yet. Add the one of your company with [1]."
|
||||||
|
msgstr ""
|
||||||
|
"ワークスペースはまだありません。[1] で会社のワークスペースを追加してくださ"
|
||||||
|
"い。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:233
|
||||||
|
msgid "off"
|
||||||
|
msgstr "オフ"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:234
|
||||||
|
msgid "home folder"
|
||||||
|
msgstr "ホームフォルダー"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:237
|
||||||
|
msgid "Documents"
|
||||||
|
msgstr "ドキュメント"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:238
|
||||||
|
msgid "Downloads"
|
||||||
|
msgstr "ダウンロード"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:243
|
||||||
|
msgid "not found"
|
||||||
|
msgstr "見つかりません"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:246
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "automatic (%d %%)"
|
||||||
|
msgstr "自動 (%d %%)"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:248
|
||||||
|
msgid "classic"
|
||||||
|
msgstr "クラシック"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:249
|
||||||
|
msgid "GFX"
|
||||||
|
msgstr "GFX"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:250
|
||||||
|
msgid "always"
|
||||||
|
msgstr "常に"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:252
|
||||||
|
msgid "automatic"
|
||||||
|
msgstr "自動"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:261
|
||||||
|
msgid ""
|
||||||
|
"The apps only see the files on the server. Pick a folder to open and save "
|
||||||
|
"files of this computer in them."
|
||||||
|
msgstr ""
|
||||||
|
"アプリはサーバー上のファイルしか見えません。フォルダーを選ぶと、このコン"
|
||||||
|
"ピューターのファイルを開いたり保存したりできます。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:262
|
||||||
|
msgid ""
|
||||||
|
"The apps can open and save files in this folder. It shows up as a drive in "
|
||||||
|
"them."
|
||||||
|
msgstr ""
|
||||||
|
"アプリはこのフォルダーのファイルを開いたり保存したりできます。アプリ内ではド"
|
||||||
|
"ライブとして表示されます。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:263
|
||||||
|
msgid ""
|
||||||
|
"Offers the apps under \"Open with\" for their file types, for example Word "
|
||||||
|
"for .docx. The folder of the file is shared with the app while it is open."
|
||||||
|
msgstr ""
|
||||||
|
"ファイルの種類ごとに「アプリケーションで開く」にアプリを表示します"
|
||||||
|
"(例: .docx なら Word)。ファイルを開いている間、そのフォルダーが共有されま"
|
||||||
|
"す。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:264
|
||||||
|
msgid ""
|
||||||
|
"How big the apps are drawn. Automatic follows the scale of your desktop, so "
|
||||||
|
"they are sharp and the right size."
|
||||||
|
msgstr ""
|
||||||
|
"アプリを描く大きさです。自動ではデスクトップの拡大率に合わせるので、くっきり"
|
||||||
|
"と適切な大きさになります。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:265
|
||||||
|
msgid "Plays the sound of the apps here, for calls and videos."
|
||||||
|
msgstr "アプリの音をこのコンピューターで再生します。通話や動画に。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:266
|
||||||
|
msgid ""
|
||||||
|
"Paints every window right. But some servers end the session right after "
|
||||||
|
"signing in, then go back to GFX."
|
||||||
|
msgstr "すべてのウィンドウを正しく描きます。ただしサインイン直後にセッションを切るサーバーもあり、その場合は GFX に戻してください。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:267
|
||||||
|
msgid ""
|
||||||
|
"Works with every server. FreeRDP may not repaint a window after it was "
|
||||||
|
"minimized or resized."
|
||||||
|
msgstr "どのサーバーでも動きます。最小化やサイズ変更の後、FreeRDP がウィンドウを描き直さないことがあります。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:268
|
||||||
|
msgid ""
|
||||||
|
"Always waits before the second sign-in. For when the second prompt does not "
|
||||||
|
"come with automatic."
|
||||||
|
msgstr ""
|
||||||
|
"2 回目のサインインの前に必ず待ちます。自動で 2 回目の確認が来ないときに。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:269
|
||||||
|
msgid "Never waits. Right when your company has no MFA."
|
||||||
|
msgstr "待ちません。会社が MFA を使っていないとき向けです。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:270
|
||||||
|
msgid ""
|
||||||
|
"Many servers with MFA need a second sign-in, and its prompt only comes after "
|
||||||
|
"a short wait. Automatic waits when the first sign-in took a while."
|
||||||
|
msgstr ""
|
||||||
|
"MFA を使う多くのサーバーでは 2 回目のサインインが必要で、その確認は少し待たな"
|
||||||
|
"いと届きません。自動では、1 回目に時間がかかったときに待ちます。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:273
|
||||||
|
msgid ""
|
||||||
|
"Loads the list of apps again once a day, so new apps show up by themselves."
|
||||||
|
msgstr ""
|
||||||
|
"1 日 1 回アプリの一覧を読み込み直すので、新しいアプリが自動で現れます。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:275 src/lib/menu.sh:330
|
||||||
|
msgid "Needs a systemd user session."
|
||||||
|
msgstr "systemd のユーザーセッションが必要です。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:322 src/lib/menu.sh:325
|
||||||
|
msgid "Could not save the setting."
|
||||||
|
msgstr "設定を保存できませんでした。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:371 src/lib/menu.sh:609
|
||||||
|
msgid "Settings"
|
||||||
|
msgstr "設定"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:372
|
||||||
|
msgid "↑↓ select ←→ or Space: change q: back"
|
||||||
|
msgstr "↑↓ 選択 ←→ またはスペース: 変更 q: 戻る"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:373
|
||||||
|
msgid "ON"
|
||||||
|
msgstr "オン"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:374
|
||||||
|
msgid "OFF"
|
||||||
|
msgstr "オフ"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:456 src/lib/menu.sh:608
|
||||||
|
msgid "Workspaces"
|
||||||
|
msgstr "ワークスペース"
|
||||||
|
|
||||||
|
#. TRANSLATORS: keep the letters, they are the keys.
|
||||||
|
#: src/lib/menu.sh:458
|
||||||
|
msgid ""
|
||||||
|
"↑↓ select r: refresh s: sign in again d: remove a: add q: back"
|
||||||
|
msgstr "↑↓ 選択 r: 更新 s: 再サインイン d: 削除 a: 追加 q: 戻る"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:459
|
||||||
|
msgid "No workspace yet. Press a to add one."
|
||||||
|
msgstr "ワークスペースはまだありません。a で追加できます。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:484
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "refreshed %s"
|
||||||
|
msgstr "%sに更新"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:512
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Remove \"%s\" and its apps?"
|
||||||
|
msgstr "「%s」とそのアプリを削除しますか?"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:536 src/lib/menu.sh:607
|
||||||
|
msgid "Start an app"
|
||||||
|
msgstr "アプリを起動する"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:537
|
||||||
|
msgid "↑↓ select Enter: start q: back"
|
||||||
|
msgstr "↑↓ 選択 Enter: 起動 q: 戻る"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:538
|
||||||
|
msgid "No apps yet. Add a workspace first."
|
||||||
|
msgstr "アプリはまだありません。先にワークスペースを追加してください。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:579
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s is starting. It opens in a window of its own."
|
||||||
|
msgstr "%s を起動しています。専用のウィンドウで開きます。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:606
|
||||||
|
msgid "Add a workspace"
|
||||||
|
msgstr "ワークスペースを追加"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:610
|
||||||
|
msgid "Quit"
|
||||||
|
msgstr "終了"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:163
|
||||||
|
msgid "The server did not accept the user name or password."
|
||||||
|
msgstr "サーバーがユーザー名またはパスワードを受け付けませんでした。"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:164
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not reach the server: %s"
|
||||||
|
msgstr "サーバーに接続できませんでした: %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:165
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "There is no RD Web feed at %s."
|
||||||
|
msgstr "%s に RD Web のフィードはありません。"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:166
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not load the apps: %s"
|
||||||
|
msgstr "アプリを読み込めませんでした: %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:282
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Remote desktop of %s"
|
||||||
|
msgstr "%s のリモートデスクトップ"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:284
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s from %s"
|
||||||
|
msgstr "%s(%s)"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Apps"
|
||||||
|
msgstr "アプリ"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Share a folder"
|
||||||
|
msgstr "フォルダーを共有"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Open files with the apps"
|
||||||
|
msgstr "ファイルをアプリで開く"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Size"
|
||||||
|
msgstr "サイズ"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Sound"
|
||||||
|
msgstr "音声"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Graphics"
|
||||||
|
msgstr "グラフィック"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Signing in"
|
||||||
|
msgstr "サインイン"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Wait for the second MFA prompt"
|
||||||
|
msgstr "2 回目の MFA 確認を待つ"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Refresh the apps every day"
|
||||||
|
msgstr "アプリを毎日更新"
|
||||||
@@ -0,0 +1,547 @@
|
|||||||
|
# Korean translation for rdfeed.
|
||||||
|
# Copyright (C) 2026 Felitendo
|
||||||
|
# This file is distributed under the same license as rdfeed.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: rdfeed 1.0.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: https://github.com/LoonixTools/rdfeed/issues\n"
|
||||||
|
"POT-Creation-Date: 2026-10-02 10:29+0200\n"
|
||||||
|
"PO-Revision-Date: 2026-10-02 09:30+0200\n"
|
||||||
|
"Last-Translator: Felitendo\n"
|
||||||
|
"Language-Team: Korean\n"
|
||||||
|
"Language: ko\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: src/rdfeed:79 src/rdfeed:103
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not write to %s."
|
||||||
|
msgstr "%s에 쓰지 못했습니다."
|
||||||
|
|
||||||
|
#: src/rdfeed:82
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Signing in to %s…"
|
||||||
|
msgstr "%s에 로그인하는 중…"
|
||||||
|
|
||||||
|
#: src/rdfeed:100
|
||||||
|
msgid ""
|
||||||
|
"The password could not be saved in the keyring, so each app asks for it."
|
||||||
|
msgstr "비밀번호를 키링에 저장하지 못해 앱마다 비밀번호를 묻습니다."
|
||||||
|
|
||||||
|
#: src/rdfeed:111
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "1 app from %s is now in your app menu."
|
||||||
|
msgstr "%s의 앱 1개가 앱 메뉴에 추가되었습니다."
|
||||||
|
|
||||||
|
#: src/rdfeed:113
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d apps from %s are now in your app menu."
|
||||||
|
msgstr "앱 %d개(%s)가 앱 메뉴에 추가되었습니다."
|
||||||
|
|
||||||
|
#: src/rdfeed:129
|
||||||
|
msgid ""
|
||||||
|
"Your IT department has the address. A work e-mail address often works too."
|
||||||
|
msgstr ""
|
||||||
|
"주소는 IT 부서에서 알려 줍니다. 회사 이메일 주소로도 되는 경우가 많습니다."
|
||||||
|
|
||||||
|
#: src/rdfeed:130
|
||||||
|
msgid "Feed address, server or e-mail:"
|
||||||
|
msgstr "피드 주소, 서버 또는 이메일:"
|
||||||
|
|
||||||
|
#: src/rdfeed:137
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No feed is registered for %s. Ask your IT department for the address."
|
||||||
|
msgstr "%s에 등록된 피드가 없습니다. IT 부서에 주소를 물어보세요."
|
||||||
|
|
||||||
|
#: src/rdfeed:142 src/rdfeed:156
|
||||||
|
msgid "User name (DOMAIN\\name or name@domain):"
|
||||||
|
msgstr "사용자 이름 (DOMAIN\\name 또는 name@domain):"
|
||||||
|
|
||||||
|
#: src/rdfeed:146 src/rdfeed:159
|
||||||
|
msgid "Password:"
|
||||||
|
msgstr "비밀번호:"
|
||||||
|
|
||||||
|
#: src/rdfeed:178 src/rdfeed:236
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No workspace yet. Add one with `%s add`."
|
||||||
|
msgstr "아직 작업 공간이 없습니다. `%s add`로 추가하세요."
|
||||||
|
|
||||||
|
#: src/rdfeed:187 src/lib/launch.sh:220
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No password saved for %s. Sign in again in rdfeed."
|
||||||
|
msgstr "%s의 비밀번호가 저장되어 있지 않습니다. rdfeed에서 다시 로그인하세요."
|
||||||
|
|
||||||
|
#: src/rdfeed:191 src/lib/launch.sh:149
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Password for %s:"
|
||||||
|
msgstr "%s 비밀번호:"
|
||||||
|
|
||||||
|
#: src/rdfeed:195
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Refreshing %s…"
|
||||||
|
msgstr "%s 새로 고치는 중…"
|
||||||
|
|
||||||
|
#: src/rdfeed:205
|
||||||
|
msgid "The password no longer works. Sign in again in rdfeed."
|
||||||
|
msgstr "비밀번호가 더 이상 맞지 않습니다. rdfeed에서 다시 로그인하세요."
|
||||||
|
|
||||||
|
#: src/rdfeed:214
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s: %d apps"
|
||||||
|
msgstr "%s: 앱 %d개"
|
||||||
|
|
||||||
|
#: src/rdfeed:222
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Which workspace? See `%s list` for the names."
|
||||||
|
msgstr "어느 작업 공간인가요? 이름은 `%s list`에서 볼 수 있습니다."
|
||||||
|
|
||||||
|
#: src/rdfeed:229
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s and its apps are removed."
|
||||||
|
msgstr "%s와(과) 해당 앱을 삭제했습니다."
|
||||||
|
|
||||||
|
#: src/rdfeed:253
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Which app? See `%s list`."
|
||||||
|
msgstr "어느 앱인가요? `%s list`를 보세요."
|
||||||
|
|
||||||
|
#: src/rdfeed:268 src/lib/launch.sh:199
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "There is no app %s. See `%s list`."
|
||||||
|
msgstr "%s 앱이 없습니다. `%s list`를 보세요."
|
||||||
|
|
||||||
|
#: src/rdfeed:270
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Several workspaces have %s. Write it as WORKSPACE/APP, see `%s list`."
|
||||||
|
msgstr ""
|
||||||
|
"여러 작업 공간에 %s이(가) 있습니다. 작업공간/앱 형식으로 쓰세요. `%s list`를 "
|
||||||
|
"보세요."
|
||||||
|
|
||||||
|
#: src/rdfeed:278
|
||||||
|
msgid "Usage: rdfeed [command]"
|
||||||
|
msgstr "사용법: rdfeed [명령]"
|
||||||
|
|
||||||
|
#: src/rdfeed:280
|
||||||
|
msgid "Commands:"
|
||||||
|
msgstr "명령:"
|
||||||
|
|
||||||
|
#: src/rdfeed:281
|
||||||
|
msgid "Add the workspace of your company"
|
||||||
|
msgstr "회사의 작업 공간 추가"
|
||||||
|
|
||||||
|
#: src/rdfeed:282
|
||||||
|
msgid "Load the apps again"
|
||||||
|
msgstr "앱 다시 불러오기"
|
||||||
|
|
||||||
|
#: src/rdfeed:283
|
||||||
|
msgid "List the workspaces and their apps"
|
||||||
|
msgstr "작업 공간과 앱 목록 보기"
|
||||||
|
|
||||||
|
#: src/rdfeed:284
|
||||||
|
msgid "Start an app, or open a file with it"
|
||||||
|
msgstr "앱 시작 또는 파일 열기"
|
||||||
|
|
||||||
|
#: src/rdfeed:285
|
||||||
|
msgid "Remove a workspace and its apps"
|
||||||
|
msgstr "작업 공간과 앱 삭제"
|
||||||
|
|
||||||
|
#: src/rdfeed:286
|
||||||
|
msgid "Show this help"
|
||||||
|
msgstr "이 도움말 보기"
|
||||||
|
|
||||||
|
#: src/rdfeed:287
|
||||||
|
msgid "Show the version"
|
||||||
|
msgstr "버전 보기"
|
||||||
|
|
||||||
|
#: src/rdfeed:289
|
||||||
|
msgid "Without a command an interactive menu is shown."
|
||||||
|
msgstr "명령 없이 실행하면 대화형 메뉴가 나옵니다."
|
||||||
|
|
||||||
|
#: src/rdfeed:313
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Unknown command: %s"
|
||||||
|
msgstr "알 수 없는 명령: %s"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:174 src/lib/menu.sh:251
|
||||||
|
msgid "never"
|
||||||
|
msgstr "없음"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:184
|
||||||
|
msgid "just now"
|
||||||
|
msgstr "방금"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:186
|
||||||
|
msgid "1 minute ago"
|
||||||
|
msgstr "1분 전"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:189
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d minutes ago"
|
||||||
|
msgstr "%d분 전"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:191
|
||||||
|
msgid "1 hour ago"
|
||||||
|
msgstr "1시간 전"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:194
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d hours ago"
|
||||||
|
msgstr "%d시간 전"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:196
|
||||||
|
msgid "1 day ago"
|
||||||
|
msgstr "1일 전"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:199
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d days ago"
|
||||||
|
msgstr "%d일 전"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:169
|
||||||
|
msgid ""
|
||||||
|
"Your password has expired. Change it on Windows or on the RD Web page, then "
|
||||||
|
"sign in again in rdfeed."
|
||||||
|
msgstr ""
|
||||||
|
"비밀번호가 만료되었습니다. Windows나 RD Web 페이지에서 바꾼 다음 rdfeed에서 "
|
||||||
|
"다시 로그인하세요."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:171
|
||||||
|
msgid "Your account is locked. Ask your IT department."
|
||||||
|
msgstr "계정이 잠겼습니다. IT 부서에 문의하세요."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:173
|
||||||
|
msgid "The server did not accept the password. Sign in again in rdfeed."
|
||||||
|
msgstr "서버가 비밀번호를 받아들이지 않았습니다. rdfeed에서 다시 로그인하세요."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:175
|
||||||
|
msgid "Could not reach the server. Check the internet connection."
|
||||||
|
msgstr "서버에 연결하지 못했습니다. 인터넷 연결을 확인하세요."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:178
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "FreeRDP stopped: %s"
|
||||||
|
msgstr "FreeRDP가 멈췄습니다: %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:205 src/lib/launch.sh:206
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "File not found: %s"
|
||||||
|
msgstr "파일을 찾을 수 없습니다: %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:212 src/lib/launch.sh:213
|
||||||
|
msgid ""
|
||||||
|
"FreeRDP 3 is missing. Install it to start the apps (the package is called "
|
||||||
|
"freerdp)."
|
||||||
|
msgstr ""
|
||||||
|
"FreeRDP 3이 없습니다. 앱을 시작하려면 설치하세요(패키지 이름은 freerdp)."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:277
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Opening %s in %s"
|
||||||
|
msgstr "%s 파일을 %s(으)로 여는 중"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:278
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "If it does not open by itself, open it in the app from the drive %s."
|
||||||
|
msgstr "저절로 열리지 않으면 앱에서 %s 드라이브의 파일을 여세요."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:280
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Starting %s"
|
||||||
|
msgstr "%s 시작하는 중"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:281
|
||||||
|
msgid ""
|
||||||
|
"If your company uses MFA, confirm the sign-in in your authenticator app."
|
||||||
|
msgstr "회사에서 MFA를 쓴다면 인증 앱에서 로그인을 승인하세요."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:290
|
||||||
|
msgid "A second sign-in is coming"
|
||||||
|
msgstr "두 번째 로그인이 옵니다"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:291
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid ""
|
||||||
|
"Confirm the second prompt in your authenticator app too. It comes in about "
|
||||||
|
"%d seconds."
|
||||||
|
msgstr "인증 앱에서 두 번째 요청도 승인하세요. 약 %d초 뒤에 옵니다."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:301
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s could not start"
|
||||||
|
msgstr "%s을(를) 시작하지 못했습니다"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:303
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Log: %s"
|
||||||
|
msgstr "로그: %s"
|
||||||
|
|
||||||
|
#. TRANSLATORS: the letter after "[" is the key for yes. y works too.
|
||||||
|
#: src/lib/menu.sh:93
|
||||||
|
msgid "[y/N]"
|
||||||
|
msgstr "[y/N]"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:163
|
||||||
|
msgid "1 app"
|
||||||
|
msgstr "앱 1개"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:165
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d apps"
|
||||||
|
msgstr "앱 %d개"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:190
|
||||||
|
msgid "No workspace yet. Add the one of your company with [1]."
|
||||||
|
msgstr "아직 작업 공간이 없습니다. [1]로 회사의 작업 공간을 추가하세요."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:233
|
||||||
|
msgid "off"
|
||||||
|
msgstr "꺼짐"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:234
|
||||||
|
msgid "home folder"
|
||||||
|
msgstr "홈 폴더"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:237
|
||||||
|
msgid "Documents"
|
||||||
|
msgstr "문서"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:238
|
||||||
|
msgid "Downloads"
|
||||||
|
msgstr "다운로드"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:243
|
||||||
|
msgid "not found"
|
||||||
|
msgstr "찾을 수 없음"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:246
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "automatic (%d %%)"
|
||||||
|
msgstr "자동 (%d%%)"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:248
|
||||||
|
msgid "classic"
|
||||||
|
msgstr "클래식"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:249
|
||||||
|
msgid "GFX"
|
||||||
|
msgstr "GFX"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:250
|
||||||
|
msgid "always"
|
||||||
|
msgstr "항상"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:252
|
||||||
|
msgid "automatic"
|
||||||
|
msgstr "자동"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:261
|
||||||
|
msgid ""
|
||||||
|
"The apps only see the files on the server. Pick a folder to open and save "
|
||||||
|
"files of this computer in them."
|
||||||
|
msgstr ""
|
||||||
|
"앱은 서버의 파일만 봅니다. 폴더를 고르면 이 컴퓨터의 파일을 열고 저장할 수 있"
|
||||||
|
"습니다."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:262
|
||||||
|
msgid ""
|
||||||
|
"The apps can open and save files in this folder. It shows up as a drive in "
|
||||||
|
"them."
|
||||||
|
msgstr ""
|
||||||
|
"앱이 이 폴더의 파일을 열고 저장할 수 있습니다. 앱에서는 드라이브로 보입니다."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:263
|
||||||
|
msgid ""
|
||||||
|
"Offers the apps under \"Open with\" for their file types, for example Word "
|
||||||
|
"for .docx. The folder of the file is shared with the app while it is open."
|
||||||
|
msgstr ""
|
||||||
|
"파일 형식에 맞게 \"연결 프로그램\"에 앱을 표시합니다(예: .docx는 Word). 파일"
|
||||||
|
"이 열려 있는 동안 해당 폴더가 공유됩니다."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:264
|
||||||
|
msgid ""
|
||||||
|
"How big the apps are drawn. Automatic follows the scale of your desktop, so "
|
||||||
|
"they are sharp and the right size."
|
||||||
|
msgstr ""
|
||||||
|
"앱을 그리는 크기입니다. 자동은 데스크톱 배율을 따르므로 선명하고 알맞은 크기"
|
||||||
|
"로 보입니다."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:265
|
||||||
|
msgid "Plays the sound of the apps here, for calls and videos."
|
||||||
|
msgstr "앱의 소리를 이 컴퓨터에서 재생합니다. 통화와 동영상에 필요합니다."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:266
|
||||||
|
msgid ""
|
||||||
|
"Paints every window right. But some servers end the session right after "
|
||||||
|
"signing in, then go back to GFX."
|
||||||
|
msgstr "모든 창을 올바르게 그립니다. 하지만 로그인 직후 세션을 끊는 서버도 있으니 그때는 GFX로 돌아가세요."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:267
|
||||||
|
msgid ""
|
||||||
|
"Works with every server. FreeRDP may not repaint a window after it was "
|
||||||
|
"minimized or resized."
|
||||||
|
msgstr "모든 서버에서 됩니다. 최소화하거나 크기를 바꾼 창을 FreeRDP가 다시 그리지 않을 수 있습니다."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:268
|
||||||
|
msgid ""
|
||||||
|
"Always waits before the second sign-in. For when the second prompt does not "
|
||||||
|
"come with automatic."
|
||||||
|
msgstr ""
|
||||||
|
"두 번째 로그인 전에 항상 기다립니다. 자동일 때 두 번째 요청이 오지 않으면 쓰"
|
||||||
|
"세요."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:269
|
||||||
|
msgid "Never waits. Right when your company has no MFA."
|
||||||
|
msgstr "기다리지 않습니다. 회사에서 MFA를 쓰지 않을 때 알맞습니다."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:270
|
||||||
|
msgid ""
|
||||||
|
"Many servers with MFA need a second sign-in, and its prompt only comes after "
|
||||||
|
"a short wait. Automatic waits when the first sign-in took a while."
|
||||||
|
msgstr ""
|
||||||
|
"MFA를 쓰는 많은 서버는 두 번째 로그인이 필요하고, 그 요청은 잠시 기다린 뒤에"
|
||||||
|
"야 옵니다. 자동은 첫 로그인이 오래 걸렸을 때 기다립니다."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:273
|
||||||
|
msgid ""
|
||||||
|
"Loads the list of apps again once a day, so new apps show up by themselves."
|
||||||
|
msgstr "하루에 한 번 앱 목록을 다시 불러오므로 새 앱이 저절로 나타납니다."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:275 src/lib/menu.sh:330
|
||||||
|
msgid "Needs a systemd user session."
|
||||||
|
msgstr "systemd 사용자 세션이 필요합니다."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:322 src/lib/menu.sh:325
|
||||||
|
msgid "Could not save the setting."
|
||||||
|
msgstr "설정을 저장하지 못했습니다."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:371 src/lib/menu.sh:609
|
||||||
|
msgid "Settings"
|
||||||
|
msgstr "설정"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:372
|
||||||
|
msgid "↑↓ select ←→ or Space: change q: back"
|
||||||
|
msgstr "↑↓ 선택 ←→ 또는 스페이스: 변경 q: 뒤로"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:373
|
||||||
|
msgid "ON"
|
||||||
|
msgstr "켜짐"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:374
|
||||||
|
msgid "OFF"
|
||||||
|
msgstr "꺼짐"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:456 src/lib/menu.sh:608
|
||||||
|
msgid "Workspaces"
|
||||||
|
msgstr "작업 공간"
|
||||||
|
|
||||||
|
#. TRANSLATORS: keep the letters, they are the keys.
|
||||||
|
#: src/lib/menu.sh:458
|
||||||
|
msgid ""
|
||||||
|
"↑↓ select r: refresh s: sign in again d: remove a: add q: back"
|
||||||
|
msgstr "↑↓ 선택 r: 새로 고침 s: 다시 로그인 d: 삭제 a: 추가 q: 뒤로"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:459
|
||||||
|
msgid "No workspace yet. Press a to add one."
|
||||||
|
msgstr "아직 작업 공간이 없습니다. a를 눌러 추가하세요."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:484
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "refreshed %s"
|
||||||
|
msgstr "%s 새로 고침"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:512
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Remove \"%s\" and its apps?"
|
||||||
|
msgstr "\"%s\"와(과) 해당 앱을 삭제할까요?"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:536 src/lib/menu.sh:607
|
||||||
|
msgid "Start an app"
|
||||||
|
msgstr "앱 시작"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:537
|
||||||
|
msgid "↑↓ select Enter: start q: back"
|
||||||
|
msgstr "↑↓ 선택 Enter: 시작 q: 뒤로"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:538
|
||||||
|
msgid "No apps yet. Add a workspace first."
|
||||||
|
msgstr "아직 앱이 없습니다. 먼저 작업 공간을 추가하세요."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:579
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s is starting. It opens in a window of its own."
|
||||||
|
msgstr "%s을(를) 시작하는 중입니다. 별도 창으로 열립니다."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:606
|
||||||
|
msgid "Add a workspace"
|
||||||
|
msgstr "작업 공간 추가"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:610
|
||||||
|
msgid "Quit"
|
||||||
|
msgstr "끝내기"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:163
|
||||||
|
msgid "The server did not accept the user name or password."
|
||||||
|
msgstr "서버가 사용자 이름이나 비밀번호를 받아들이지 않았습니다."
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:164
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not reach the server: %s"
|
||||||
|
msgstr "서버에 연결하지 못했습니다: %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:165
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "There is no RD Web feed at %s."
|
||||||
|
msgstr "%s에 RD Web 피드가 없습니다."
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:166
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not load the apps: %s"
|
||||||
|
msgstr "앱을 불러오지 못했습니다: %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:282
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Remote desktop of %s"
|
||||||
|
msgstr "%s 원격 데스크톱"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:284
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s from %s"
|
||||||
|
msgstr "%s (%s)"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Apps"
|
||||||
|
msgstr "앱"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Share a folder"
|
||||||
|
msgstr "폴더 공유"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Open files with the apps"
|
||||||
|
msgstr "앱으로 파일 열기"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Size"
|
||||||
|
msgstr "크기"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Sound"
|
||||||
|
msgstr "소리"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Graphics"
|
||||||
|
msgstr "그래픽"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Signing in"
|
||||||
|
msgstr "로그인"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Wait for the second MFA prompt"
|
||||||
|
msgstr "두 번째 MFA 요청 기다리기"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Refresh the apps every day"
|
||||||
|
msgstr "매일 앱 새로 고침"
|
||||||
@@ -0,0 +1,562 @@
|
|||||||
|
# Dutch translation for rdfeed.
|
||||||
|
# Copyright (C) 2026 Felitendo
|
||||||
|
# This file is distributed under the same license as rdfeed.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: rdfeed 1.0.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: https://github.com/LoonixTools/rdfeed/issues\n"
|
||||||
|
"POT-Creation-Date: 2026-10-02 10:29+0200\n"
|
||||||
|
"PO-Revision-Date: 2026-10-02 09:30+0200\n"
|
||||||
|
"Last-Translator: Felitendo\n"
|
||||||
|
"Language-Team: Dutch\n"
|
||||||
|
"Language: nl\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: src/rdfeed:79 src/rdfeed:103
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not write to %s."
|
||||||
|
msgstr "Kon niet schrijven naar %s."
|
||||||
|
|
||||||
|
#: src/rdfeed:82
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Signing in to %s…"
|
||||||
|
msgstr "Aanmelden bij %s…"
|
||||||
|
|
||||||
|
#: src/rdfeed:100
|
||||||
|
msgid ""
|
||||||
|
"The password could not be saved in the keyring, so each app asks for it."
|
||||||
|
msgstr ""
|
||||||
|
"Het wachtwoord kon niet in de sleutelbos worden opgeslagen, dus elke app "
|
||||||
|
"vraagt erom."
|
||||||
|
|
||||||
|
#: src/rdfeed:111
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "1 app from %s is now in your app menu."
|
||||||
|
msgstr "1 app van %s staat nu in je app-menu."
|
||||||
|
|
||||||
|
#: src/rdfeed:113
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d apps from %s are now in your app menu."
|
||||||
|
msgstr "%d apps van %s staan nu in je app-menu."
|
||||||
|
|
||||||
|
#: src/rdfeed:129
|
||||||
|
msgid ""
|
||||||
|
"Your IT department has the address. A work e-mail address often works too."
|
||||||
|
msgstr "Je IT-afdeling heeft het adres. Vaak werkt je werk-e-mailadres ook."
|
||||||
|
|
||||||
|
#: src/rdfeed:130
|
||||||
|
msgid "Feed address, server or e-mail:"
|
||||||
|
msgstr "Feedadres, server of e-mail:"
|
||||||
|
|
||||||
|
#: src/rdfeed:137
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No feed is registered for %s. Ask your IT department for the address."
|
||||||
|
msgstr ""
|
||||||
|
"Er is geen feed geregistreerd voor %s. Vraag je IT-afdeling om het adres."
|
||||||
|
|
||||||
|
#: src/rdfeed:142 src/rdfeed:156
|
||||||
|
msgid "User name (DOMAIN\\name or name@domain):"
|
||||||
|
msgstr "Gebruikersnaam (DOMEIN\\naam of naam@domein):"
|
||||||
|
|
||||||
|
#: src/rdfeed:146 src/rdfeed:159
|
||||||
|
msgid "Password:"
|
||||||
|
msgstr "Wachtwoord:"
|
||||||
|
|
||||||
|
#: src/rdfeed:178 src/rdfeed:236
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No workspace yet. Add one with `%s add`."
|
||||||
|
msgstr "Nog geen werkruimte. Voeg er een toe met `%s add`."
|
||||||
|
|
||||||
|
#: src/rdfeed:187 src/lib/launch.sh:220
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No password saved for %s. Sign in again in rdfeed."
|
||||||
|
msgstr "Geen wachtwoord opgeslagen voor %s. Meld je opnieuw aan in rdfeed."
|
||||||
|
|
||||||
|
#: src/rdfeed:191 src/lib/launch.sh:149
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Password for %s:"
|
||||||
|
msgstr "Wachtwoord voor %s:"
|
||||||
|
|
||||||
|
#: src/rdfeed:195
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Refreshing %s…"
|
||||||
|
msgstr "%s vernieuwen…"
|
||||||
|
|
||||||
|
#: src/rdfeed:205
|
||||||
|
msgid "The password no longer works. Sign in again in rdfeed."
|
||||||
|
msgstr "Het wachtwoord werkt niet meer. Meld je opnieuw aan in rdfeed."
|
||||||
|
|
||||||
|
#: src/rdfeed:214
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s: %d apps"
|
||||||
|
msgstr "%s: %d apps"
|
||||||
|
|
||||||
|
#: src/rdfeed:222
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Which workspace? See `%s list` for the names."
|
||||||
|
msgstr "Welke werkruimte? De namen staan in `%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:229
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s and its apps are removed."
|
||||||
|
msgstr "%s en de apps ervan zijn verwijderd."
|
||||||
|
|
||||||
|
#: src/rdfeed:253
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Which app? See `%s list`."
|
||||||
|
msgstr "Welke app? Zie `%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:268 src/lib/launch.sh:199
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "There is no app %s. See `%s list`."
|
||||||
|
msgstr "Er is geen app %s. Zie `%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:270
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Several workspaces have %s. Write it as WORKSPACE/APP, see `%s list`."
|
||||||
|
msgstr ""
|
||||||
|
"Meerdere werkruimtes hebben %s. Schrijf het als WERKRUIMTE/APP, zie `%s "
|
||||||
|
"list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:278
|
||||||
|
msgid "Usage: rdfeed [command]"
|
||||||
|
msgstr "Gebruik: rdfeed [opdracht]"
|
||||||
|
|
||||||
|
#: src/rdfeed:280
|
||||||
|
msgid "Commands:"
|
||||||
|
msgstr "Opdrachten:"
|
||||||
|
|
||||||
|
#: src/rdfeed:281
|
||||||
|
msgid "Add the workspace of your company"
|
||||||
|
msgstr "De werkruimte van je bedrijf toevoegen"
|
||||||
|
|
||||||
|
#: src/rdfeed:282
|
||||||
|
msgid "Load the apps again"
|
||||||
|
msgstr "De apps opnieuw laden"
|
||||||
|
|
||||||
|
#: src/rdfeed:283
|
||||||
|
msgid "List the workspaces and their apps"
|
||||||
|
msgstr "Werkruimtes en hun apps tonen"
|
||||||
|
|
||||||
|
#: src/rdfeed:284
|
||||||
|
msgid "Start an app, or open a file with it"
|
||||||
|
msgstr "Een app starten, of er een bestand mee openen"
|
||||||
|
|
||||||
|
#: src/rdfeed:285
|
||||||
|
msgid "Remove a workspace and its apps"
|
||||||
|
msgstr "Een werkruimte en de apps ervan verwijderen"
|
||||||
|
|
||||||
|
#: src/rdfeed:286
|
||||||
|
msgid "Show this help"
|
||||||
|
msgstr "Deze hulp tonen"
|
||||||
|
|
||||||
|
#: src/rdfeed:287
|
||||||
|
msgid "Show the version"
|
||||||
|
msgstr "De versie tonen"
|
||||||
|
|
||||||
|
#: src/rdfeed:289
|
||||||
|
msgid "Without a command an interactive menu is shown."
|
||||||
|
msgstr "Zonder opdracht verschijnt een interactief menu."
|
||||||
|
|
||||||
|
#: src/rdfeed:313
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Unknown command: %s"
|
||||||
|
msgstr "Onbekende opdracht: %s"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:174 src/lib/menu.sh:251
|
||||||
|
msgid "never"
|
||||||
|
msgstr "nooit"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:184
|
||||||
|
msgid "just now"
|
||||||
|
msgstr "zojuist"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:186
|
||||||
|
msgid "1 minute ago"
|
||||||
|
msgstr "1 minuut geleden"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:189
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d minutes ago"
|
||||||
|
msgstr "%d minuten geleden"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:191
|
||||||
|
msgid "1 hour ago"
|
||||||
|
msgstr "1 uur geleden"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:194
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d hours ago"
|
||||||
|
msgstr "%d uur geleden"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:196
|
||||||
|
msgid "1 day ago"
|
||||||
|
msgstr "1 dag geleden"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:199
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d days ago"
|
||||||
|
msgstr "%d dagen geleden"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:169
|
||||||
|
msgid ""
|
||||||
|
"Your password has expired. Change it on Windows or on the RD Web page, then "
|
||||||
|
"sign in again in rdfeed."
|
||||||
|
msgstr ""
|
||||||
|
"Je wachtwoord is verlopen. Wijzig het in Windows of op de RD Web-pagina en "
|
||||||
|
"meld je daarna opnieuw aan in rdfeed."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:171
|
||||||
|
msgid "Your account is locked. Ask your IT department."
|
||||||
|
msgstr "Je account is geblokkeerd. Vraag het je IT-afdeling."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:173
|
||||||
|
msgid "The server did not accept the password. Sign in again in rdfeed."
|
||||||
|
msgstr ""
|
||||||
|
"De server heeft het wachtwoord niet geaccepteerd. Meld je opnieuw aan in "
|
||||||
|
"rdfeed."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:175
|
||||||
|
msgid "Could not reach the server. Check the internet connection."
|
||||||
|
msgstr "Kon de server niet bereiken. Controleer de internetverbinding."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:178
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "FreeRDP stopped: %s"
|
||||||
|
msgstr "FreeRDP is gestopt: %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:205 src/lib/launch.sh:206
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "File not found: %s"
|
||||||
|
msgstr "Bestand niet gevonden: %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:212 src/lib/launch.sh:213
|
||||||
|
msgid ""
|
||||||
|
"FreeRDP 3 is missing. Install it to start the apps (the package is called "
|
||||||
|
"freerdp)."
|
||||||
|
msgstr ""
|
||||||
|
"FreeRDP 3 ontbreekt. Installeer het om de apps te starten (het pakket heet "
|
||||||
|
"freerdp)."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:277
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Opening %s in %s"
|
||||||
|
msgstr "%s wordt geopend in %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:278
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "If it does not open by itself, open it in the app from the drive %s."
|
||||||
|
msgstr ""
|
||||||
|
"Gaat het niet vanzelf open, open het dan in de app vanaf het station %s."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:280
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Starting %s"
|
||||||
|
msgstr "%s wordt gestart"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:281
|
||||||
|
msgid ""
|
||||||
|
"If your company uses MFA, confirm the sign-in in your authenticator app."
|
||||||
|
msgstr ""
|
||||||
|
"Gebruikt je bedrijf MFA, bevestig de aanmelding dan in je authenticator-app."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:290
|
||||||
|
msgid "A second sign-in is coming"
|
||||||
|
msgstr "Er komt een tweede aanmelding"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:291
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid ""
|
||||||
|
"Confirm the second prompt in your authenticator app too. It comes in about "
|
||||||
|
"%d seconds."
|
||||||
|
msgstr ""
|
||||||
|
"Bevestig ook het tweede verzoek in je authenticator-app. Het komt over "
|
||||||
|
"ongeveer %d seconden."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:301
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s could not start"
|
||||||
|
msgstr "%s kon niet starten"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:303
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Log: %s"
|
||||||
|
msgstr "Logboek: %s"
|
||||||
|
|
||||||
|
#. TRANSLATORS: the letter after "[" is the key for yes. y works too.
|
||||||
|
#: src/lib/menu.sh:93
|
||||||
|
msgid "[y/N]"
|
||||||
|
msgstr "[j/N]"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:163
|
||||||
|
msgid "1 app"
|
||||||
|
msgstr "1 app"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:165
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d apps"
|
||||||
|
msgstr "%d apps"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:190
|
||||||
|
msgid "No workspace yet. Add the one of your company with [1]."
|
||||||
|
msgstr "Nog geen werkruimte. Voeg die van je bedrijf toe met [1]."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:233
|
||||||
|
msgid "off"
|
||||||
|
msgstr "uit"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:234
|
||||||
|
msgid "home folder"
|
||||||
|
msgstr "thuismap"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:237
|
||||||
|
msgid "Documents"
|
||||||
|
msgstr "Documenten"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:238
|
||||||
|
msgid "Downloads"
|
||||||
|
msgstr "Downloads"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:243
|
||||||
|
msgid "not found"
|
||||||
|
msgstr "niet gevonden"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:246
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "automatic (%d %%)"
|
||||||
|
msgstr "automatisch (%d %%)"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:248
|
||||||
|
msgid "classic"
|
||||||
|
msgstr "klassiek"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:249
|
||||||
|
msgid "GFX"
|
||||||
|
msgstr "GFX"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:250
|
||||||
|
msgid "always"
|
||||||
|
msgstr "altijd"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:252
|
||||||
|
msgid "automatic"
|
||||||
|
msgstr "automatisch"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:261
|
||||||
|
msgid ""
|
||||||
|
"The apps only see the files on the server. Pick a folder to open and save "
|
||||||
|
"files of this computer in them."
|
||||||
|
msgstr ""
|
||||||
|
"De apps zien alleen de bestanden op de server. Kies een map, dan kunnen ze "
|
||||||
|
"bestanden van deze computer openen en opslaan."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:262
|
||||||
|
msgid ""
|
||||||
|
"The apps can open and save files in this folder. It shows up as a drive in "
|
||||||
|
"them."
|
||||||
|
msgstr ""
|
||||||
|
"De apps kunnen in deze map bestanden openen en opslaan. Hij verschijnt "
|
||||||
|
"daarin als station."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:263
|
||||||
|
msgid ""
|
||||||
|
"Offers the apps under \"Open with\" for their file types, for example Word "
|
||||||
|
"for .docx. The folder of the file is shared with the app while it is open."
|
||||||
|
msgstr ""
|
||||||
|
"Biedt de apps aan onder \"Openen met\" voor hun bestandstypen, bijvoorbeeld "
|
||||||
|
"Word voor .docx. De map van het bestand wordt gedeeld zolang het open is."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:264
|
||||||
|
msgid ""
|
||||||
|
"How big the apps are drawn. Automatic follows the scale of your desktop, so "
|
||||||
|
"they are sharp and the right size."
|
||||||
|
msgstr ""
|
||||||
|
"Hoe groot de apps worden getekend. Automatisch volgt de schaal van je "
|
||||||
|
"bureaublad, zo zijn ze scherp en goed van formaat."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:265
|
||||||
|
msgid "Plays the sound of the apps here, for calls and videos."
|
||||||
|
msgstr "Speelt het geluid van de apps hier af, voor gesprekken en video's."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:266
|
||||||
|
msgid ""
|
||||||
|
"Paints every window right. But some servers end the session right after "
|
||||||
|
"signing in, then go back to GFX."
|
||||||
|
msgstr "Tekent elk venster goed. Maar sommige servers beëindigen de sessie direct na het aanmelden; ga dan terug naar GFX."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:267
|
||||||
|
msgid ""
|
||||||
|
"Works with every server. FreeRDP may not repaint a window after it was "
|
||||||
|
"minimized or resized."
|
||||||
|
msgstr "Werkt met elke server. FreeRDP tekent een venster na minimaliseren of van formaat wijzigen misschien niet opnieuw."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:268
|
||||||
|
msgid ""
|
||||||
|
"Always waits before the second sign-in. For when the second prompt does not "
|
||||||
|
"come with automatic."
|
||||||
|
msgstr ""
|
||||||
|
"Wacht altijd voor de tweede aanmelding. Voor als het tweede verzoek met "
|
||||||
|
"automatisch niet komt."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:269
|
||||||
|
msgid "Never waits. Right when your company has no MFA."
|
||||||
|
msgstr "Wacht nooit. Goed als je bedrijf geen MFA gebruikt."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:270
|
||||||
|
msgid ""
|
||||||
|
"Many servers with MFA need a second sign-in, and its prompt only comes after "
|
||||||
|
"a short wait. Automatic waits when the first sign-in took a while."
|
||||||
|
msgstr ""
|
||||||
|
"Veel servers met MFA vragen een tweede aanmelding, en het verzoek daarvoor "
|
||||||
|
"komt pas na even wachten. Automatisch wacht als de eerste aanmelding even "
|
||||||
|
"duurde."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:273
|
||||||
|
msgid ""
|
||||||
|
"Loads the list of apps again once a day, so new apps show up by themselves."
|
||||||
|
msgstr ""
|
||||||
|
"Laadt de lijst met apps één keer per dag opnieuw, zo verschijnen nieuwe apps "
|
||||||
|
"vanzelf."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:275 src/lib/menu.sh:330
|
||||||
|
msgid "Needs a systemd user session."
|
||||||
|
msgstr "Vereist een systemd-gebruikerssessie."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:322 src/lib/menu.sh:325
|
||||||
|
msgid "Could not save the setting."
|
||||||
|
msgstr "Kon de instelling niet opslaan."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:371 src/lib/menu.sh:609
|
||||||
|
msgid "Settings"
|
||||||
|
msgstr "Instellingen"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:372
|
||||||
|
msgid "↑↓ select ←→ or Space: change q: back"
|
||||||
|
msgstr "↑↓ kiezen ←→ of Spatie: wijzigen q: terug"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:373
|
||||||
|
msgid "ON"
|
||||||
|
msgstr "AAN"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:374
|
||||||
|
msgid "OFF"
|
||||||
|
msgstr "UIT"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:456 src/lib/menu.sh:608
|
||||||
|
msgid "Workspaces"
|
||||||
|
msgstr "Werkruimtes"
|
||||||
|
|
||||||
|
#. TRANSLATORS: keep the letters, they are the keys.
|
||||||
|
#: src/lib/menu.sh:458
|
||||||
|
msgid ""
|
||||||
|
"↑↓ select r: refresh s: sign in again d: remove a: add q: back"
|
||||||
|
msgstr ""
|
||||||
|
"↑↓ kiezen r: vernieuwen s: opnieuw aanmelden d: verwijderen a: "
|
||||||
|
"toevoegen q: terug"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:459
|
||||||
|
msgid "No workspace yet. Press a to add one."
|
||||||
|
msgstr "Nog geen werkruimte. Druk op a om er een toe te voegen."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:484
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "refreshed %s"
|
||||||
|
msgstr "vernieuwd %s"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:512
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Remove \"%s\" and its apps?"
|
||||||
|
msgstr "\"%s\" en de apps ervan verwijderen?"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:536 src/lib/menu.sh:607
|
||||||
|
msgid "Start an app"
|
||||||
|
msgstr "Een app starten"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:537
|
||||||
|
msgid "↑↓ select Enter: start q: back"
|
||||||
|
msgstr "↑↓ kiezen Enter: starten q: terug"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:538
|
||||||
|
msgid "No apps yet. Add a workspace first."
|
||||||
|
msgstr "Nog geen apps. Voeg eerst een werkruimte toe."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:579
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s is starting. It opens in a window of its own."
|
||||||
|
msgstr "%s wordt gestart. Het opent in een eigen venster."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:606
|
||||||
|
msgid "Add a workspace"
|
||||||
|
msgstr "Werkruimte toevoegen"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:610
|
||||||
|
msgid "Quit"
|
||||||
|
msgstr "Afsluiten"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:163
|
||||||
|
msgid "The server did not accept the user name or password."
|
||||||
|
msgstr "De server heeft de gebruikersnaam of het wachtwoord niet geaccepteerd."
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:164
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not reach the server: %s"
|
||||||
|
msgstr "Kon de server niet bereiken: %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:165
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "There is no RD Web feed at %s."
|
||||||
|
msgstr "Er is geen RD Web-feed op %s."
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:166
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not load the apps: %s"
|
||||||
|
msgstr "Kon de apps niet laden: %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:282
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Remote desktop of %s"
|
||||||
|
msgstr "Extern bureaublad van %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:284
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s from %s"
|
||||||
|
msgstr "%s van %s"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Apps"
|
||||||
|
msgstr "Apps"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Share a folder"
|
||||||
|
msgstr "Map delen"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Open files with the apps"
|
||||||
|
msgstr "Bestanden openen met de apps"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Size"
|
||||||
|
msgstr "Grootte"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Sound"
|
||||||
|
msgstr "Geluid"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Graphics"
|
||||||
|
msgstr "Weergave"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Signing in"
|
||||||
|
msgstr "Aanmelden"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Wait for the second MFA prompt"
|
||||||
|
msgstr "Wachten op tweede MFA-verzoek"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Refresh the apps every day"
|
||||||
|
msgstr "Apps elke dag vernieuwen"
|
||||||
@@ -0,0 +1,555 @@
|
|||||||
|
# Polish translation for rdfeed.
|
||||||
|
# Copyright (C) 2026 Felitendo
|
||||||
|
# This file is distributed under the same license as rdfeed.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: rdfeed 1.0.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: https://github.com/LoonixTools/rdfeed/issues\n"
|
||||||
|
"POT-Creation-Date: 2026-10-02 10:29+0200\n"
|
||||||
|
"PO-Revision-Date: 2026-10-02 09:30+0200\n"
|
||||||
|
"Last-Translator: Felitendo\n"
|
||||||
|
"Language-Team: Polish\n"
|
||||||
|
"Language: pl\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: src/rdfeed:79 src/rdfeed:103
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not write to %s."
|
||||||
|
msgstr "Nie udało się zapisać w %s."
|
||||||
|
|
||||||
|
#: src/rdfeed:82
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Signing in to %s…"
|
||||||
|
msgstr "Logowanie do %s…"
|
||||||
|
|
||||||
|
#: src/rdfeed:100
|
||||||
|
msgid ""
|
||||||
|
"The password could not be saved in the keyring, so each app asks for it."
|
||||||
|
msgstr ""
|
||||||
|
"Nie udało się zapisać hasła w pęku kluczy, więc każda aplikacja o nie zapyta."
|
||||||
|
|
||||||
|
#: src/rdfeed:111
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "1 app from %s is now in your app menu."
|
||||||
|
msgstr "1 aplikacja z %s jest teraz w menu aplikacji."
|
||||||
|
|
||||||
|
#: src/rdfeed:113
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d apps from %s are now in your app menu."
|
||||||
|
msgstr "Dodano aplikacje (%d) z %s do menu aplikacji."
|
||||||
|
|
||||||
|
#: src/rdfeed:129
|
||||||
|
msgid ""
|
||||||
|
"Your IT department has the address. A work e-mail address often works too."
|
||||||
|
msgstr "Adres ma twój dział IT. Często działa też służbowy adres e-mail."
|
||||||
|
|
||||||
|
#: src/rdfeed:130
|
||||||
|
msgid "Feed address, server or e-mail:"
|
||||||
|
msgstr "Adres kanału, serwer lub e-mail:"
|
||||||
|
|
||||||
|
#: src/rdfeed:137
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No feed is registered for %s. Ask your IT department for the address."
|
||||||
|
msgstr "Dla %s nie zarejestrowano kanału. Zapytaj dział IT o adres."
|
||||||
|
|
||||||
|
#: src/rdfeed:142 src/rdfeed:156
|
||||||
|
msgid "User name (DOMAIN\\name or name@domain):"
|
||||||
|
msgstr "Nazwa użytkownika (DOMENA\\nazwa lub nazwa@domena):"
|
||||||
|
|
||||||
|
#: src/rdfeed:146 src/rdfeed:159
|
||||||
|
msgid "Password:"
|
||||||
|
msgstr "Hasło:"
|
||||||
|
|
||||||
|
#: src/rdfeed:178 src/rdfeed:236
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No workspace yet. Add one with `%s add`."
|
||||||
|
msgstr "Nie ma jeszcze obszaru roboczego. Dodaj go poleceniem `%s add`."
|
||||||
|
|
||||||
|
#: src/rdfeed:187 src/lib/launch.sh:220
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No password saved for %s. Sign in again in rdfeed."
|
||||||
|
msgstr "Brak zapisanego hasła dla %s. Zaloguj się ponownie w rdfeed."
|
||||||
|
|
||||||
|
#: src/rdfeed:191 src/lib/launch.sh:149
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Password for %s:"
|
||||||
|
msgstr "Hasło dla %s:"
|
||||||
|
|
||||||
|
#: src/rdfeed:195
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Refreshing %s…"
|
||||||
|
msgstr "Odświeżanie %s…"
|
||||||
|
|
||||||
|
#: src/rdfeed:205
|
||||||
|
msgid "The password no longer works. Sign in again in rdfeed."
|
||||||
|
msgstr "Hasło już nie działa. Zaloguj się ponownie w rdfeed."
|
||||||
|
|
||||||
|
#: src/rdfeed:214
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s: %d apps"
|
||||||
|
msgstr "%s: aplikacje: %d"
|
||||||
|
|
||||||
|
#: src/rdfeed:222
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Which workspace? See `%s list` for the names."
|
||||||
|
msgstr "Który obszar roboczy? Nazwy pokazuje `%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:229
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s and its apps are removed."
|
||||||
|
msgstr "Usunięto %s i jego aplikacje."
|
||||||
|
|
||||||
|
#: src/rdfeed:253
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Which app? See `%s list`."
|
||||||
|
msgstr "Która aplikacja? Zobacz `%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:268 src/lib/launch.sh:199
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "There is no app %s. See `%s list`."
|
||||||
|
msgstr "Nie ma aplikacji %s. Zobacz `%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:270
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Several workspaces have %s. Write it as WORKSPACE/APP, see `%s list`."
|
||||||
|
msgstr ""
|
||||||
|
"Kilka obszarów roboczych ma %s. Wpisz to jako OBSZAR/APLIKACJA, zobacz `%s "
|
||||||
|
"list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:278
|
||||||
|
msgid "Usage: rdfeed [command]"
|
||||||
|
msgstr "Użycie: rdfeed [polecenie]"
|
||||||
|
|
||||||
|
#: src/rdfeed:280
|
||||||
|
msgid "Commands:"
|
||||||
|
msgstr "Polecenia:"
|
||||||
|
|
||||||
|
#: src/rdfeed:281
|
||||||
|
msgid "Add the workspace of your company"
|
||||||
|
msgstr "Dodaj obszar roboczy swojej firmy"
|
||||||
|
|
||||||
|
#: src/rdfeed:282
|
||||||
|
msgid "Load the apps again"
|
||||||
|
msgstr "Wczytaj aplikacje ponownie"
|
||||||
|
|
||||||
|
#: src/rdfeed:283
|
||||||
|
msgid "List the workspaces and their apps"
|
||||||
|
msgstr "Wyświetl obszary robocze i ich aplikacje"
|
||||||
|
|
||||||
|
#: src/rdfeed:284
|
||||||
|
msgid "Start an app, or open a file with it"
|
||||||
|
msgstr "Uruchom aplikację lub otwórz nią plik"
|
||||||
|
|
||||||
|
#: src/rdfeed:285
|
||||||
|
msgid "Remove a workspace and its apps"
|
||||||
|
msgstr "Usuń obszar roboczy i jego aplikacje"
|
||||||
|
|
||||||
|
#: src/rdfeed:286
|
||||||
|
msgid "Show this help"
|
||||||
|
msgstr "Pokaż tę pomoc"
|
||||||
|
|
||||||
|
#: src/rdfeed:287
|
||||||
|
msgid "Show the version"
|
||||||
|
msgstr "Pokaż wersję"
|
||||||
|
|
||||||
|
#: src/rdfeed:289
|
||||||
|
msgid "Without a command an interactive menu is shown."
|
||||||
|
msgstr "Bez polecenia wyświetla się interaktywne menu."
|
||||||
|
|
||||||
|
#: src/rdfeed:313
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Unknown command: %s"
|
||||||
|
msgstr "Nieznane polecenie: %s"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:174 src/lib/menu.sh:251
|
||||||
|
msgid "never"
|
||||||
|
msgstr "nigdy"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:184
|
||||||
|
msgid "just now"
|
||||||
|
msgstr "przed chwilą"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:186
|
||||||
|
msgid "1 minute ago"
|
||||||
|
msgstr "1 minutę temu"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:189
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d minutes ago"
|
||||||
|
msgstr "%d min temu"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:191
|
||||||
|
msgid "1 hour ago"
|
||||||
|
msgstr "1 godzinę temu"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:194
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d hours ago"
|
||||||
|
msgstr "%d godz. temu"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:196
|
||||||
|
msgid "1 day ago"
|
||||||
|
msgstr "1 dzień temu"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:199
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d days ago"
|
||||||
|
msgstr "%d dni temu"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:169
|
||||||
|
msgid ""
|
||||||
|
"Your password has expired. Change it on Windows or on the RD Web page, then "
|
||||||
|
"sign in again in rdfeed."
|
||||||
|
msgstr ""
|
||||||
|
"Hasło wygasło. Zmień je w Windows lub na stronie RD Web, a potem zaloguj się "
|
||||||
|
"ponownie w rdfeed."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:171
|
||||||
|
msgid "Your account is locked. Ask your IT department."
|
||||||
|
msgstr "Konto jest zablokowane. Zapytaj dział IT."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:173
|
||||||
|
msgid "The server did not accept the password. Sign in again in rdfeed."
|
||||||
|
msgstr "Serwer nie przyjął hasła. Zaloguj się ponownie w rdfeed."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:175
|
||||||
|
msgid "Could not reach the server. Check the internet connection."
|
||||||
|
msgstr "Nie udało się połączyć z serwerem. Sprawdź połączenie z internetem."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:178
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "FreeRDP stopped: %s"
|
||||||
|
msgstr "FreeRDP zatrzymał się: %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:205 src/lib/launch.sh:206
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "File not found: %s"
|
||||||
|
msgstr "Nie znaleziono pliku: %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:212 src/lib/launch.sh:213
|
||||||
|
msgid ""
|
||||||
|
"FreeRDP 3 is missing. Install it to start the apps (the package is called "
|
||||||
|
"freerdp)."
|
||||||
|
msgstr ""
|
||||||
|
"Brakuje FreeRDP 3. Zainstaluj go, aby uruchamiać aplikacje (pakiet nazywa "
|
||||||
|
"się freerdp)."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:277
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Opening %s in %s"
|
||||||
|
msgstr "Otwieranie %s w %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:278
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "If it does not open by itself, open it in the app from the drive %s."
|
||||||
|
msgstr "Jeśli nie otworzy się sam, otwórz go w aplikacji z dysku %s."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:280
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Starting %s"
|
||||||
|
msgstr "Uruchamianie: %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:281
|
||||||
|
msgid ""
|
||||||
|
"If your company uses MFA, confirm the sign-in in your authenticator app."
|
||||||
|
msgstr ""
|
||||||
|
"Jeśli firma używa MFA, potwierdź logowanie w aplikacji uwierzytelniającej."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:290
|
||||||
|
msgid "A second sign-in is coming"
|
||||||
|
msgstr "Nadchodzi drugie logowanie"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:291
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid ""
|
||||||
|
"Confirm the second prompt in your authenticator app too. It comes in about "
|
||||||
|
"%d seconds."
|
||||||
|
msgstr ""
|
||||||
|
"Potwierdź też drugie żądanie w aplikacji uwierzytelniającej. Przyjdzie za "
|
||||||
|
"około %d s."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:301
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s could not start"
|
||||||
|
msgstr "Nie udało się uruchomić: %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:303
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Log: %s"
|
||||||
|
msgstr "Dziennik: %s"
|
||||||
|
|
||||||
|
#. TRANSLATORS: the letter after "[" is the key for yes. y works too.
|
||||||
|
#: src/lib/menu.sh:93
|
||||||
|
msgid "[y/N]"
|
||||||
|
msgstr "[t/N]"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:163
|
||||||
|
msgid "1 app"
|
||||||
|
msgstr "1 aplikacja"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:165
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d apps"
|
||||||
|
msgstr "aplikacje: %d"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:190
|
||||||
|
msgid "No workspace yet. Add the one of your company with [1]."
|
||||||
|
msgstr "Nie ma jeszcze obszaru roboczego. Dodaj obszar swojej firmy przez [1]."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:233
|
||||||
|
msgid "off"
|
||||||
|
msgstr "wył."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:234
|
||||||
|
msgid "home folder"
|
||||||
|
msgstr "folder domowy"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:237
|
||||||
|
msgid "Documents"
|
||||||
|
msgstr "Dokumenty"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:238
|
||||||
|
msgid "Downloads"
|
||||||
|
msgstr "Pobrane"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:243
|
||||||
|
msgid "not found"
|
||||||
|
msgstr "nie znaleziono"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:246
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "automatic (%d %%)"
|
||||||
|
msgstr "automatyczny (%d %%)"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:248
|
||||||
|
msgid "classic"
|
||||||
|
msgstr "klasyczny"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:249
|
||||||
|
msgid "GFX"
|
||||||
|
msgstr "GFX"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:250
|
||||||
|
msgid "always"
|
||||||
|
msgstr "zawsze"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:252
|
||||||
|
msgid "automatic"
|
||||||
|
msgstr "automatycznie"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:261
|
||||||
|
msgid ""
|
||||||
|
"The apps only see the files on the server. Pick a folder to open and save "
|
||||||
|
"files of this computer in them."
|
||||||
|
msgstr ""
|
||||||
|
"Aplikacje widzą tylko pliki na serwerze. Wybierz folder, aby mogły otwierać "
|
||||||
|
"i zapisywać pliki z tego komputera."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:262
|
||||||
|
msgid ""
|
||||||
|
"The apps can open and save files in this folder. It shows up as a drive in "
|
||||||
|
"them."
|
||||||
|
msgstr ""
|
||||||
|
"Aplikacje mogą otwierać i zapisywać pliki w tym folderze. Widzą go jako dysk."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:263
|
||||||
|
msgid ""
|
||||||
|
"Offers the apps under \"Open with\" for their file types, for example Word "
|
||||||
|
"for .docx. The folder of the file is shared with the app while it is open."
|
||||||
|
msgstr ""
|
||||||
|
"Pokazuje aplikacje w „Otwórz za pomocą” dla ich typów plików, na przykład "
|
||||||
|
"Word dla .docx. Folder pliku jest udostępniany, dopóki plik jest otwarty."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:264
|
||||||
|
msgid ""
|
||||||
|
"How big the apps are drawn. Automatic follows the scale of your desktop, so "
|
||||||
|
"they are sharp and the right size."
|
||||||
|
msgstr ""
|
||||||
|
"Jak duże są rysowane aplikacje. Automatyczny podąża za skalą pulpitu, więc "
|
||||||
|
"są ostre i mają właściwy rozmiar."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:265
|
||||||
|
msgid "Plays the sound of the apps here, for calls and videos."
|
||||||
|
msgstr "Odtwarza tutaj dźwięk aplikacji, do rozmów i filmów."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:266
|
||||||
|
msgid ""
|
||||||
|
"Paints every window right. But some servers end the session right after "
|
||||||
|
"signing in, then go back to GFX."
|
||||||
|
msgstr "Rysuje każde okno poprawnie. Ale niektóre serwery kończą sesję zaraz po zalogowaniu, wtedy wróć do GFX."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:267
|
||||||
|
msgid ""
|
||||||
|
"Works with every server. FreeRDP may not repaint a window after it was "
|
||||||
|
"minimized or resized."
|
||||||
|
msgstr "Działa z każdym serwerem. FreeRDP może nie odrysować okna po zminimalizowaniu lub zmianie rozmiaru."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:268
|
||||||
|
msgid ""
|
||||||
|
"Always waits before the second sign-in. For when the second prompt does not "
|
||||||
|
"come with automatic."
|
||||||
|
msgstr ""
|
||||||
|
"Zawsze czeka przed drugim logowaniem. Gdy przy ustawieniu automatycznym "
|
||||||
|
"drugie żądanie nie przychodzi."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:269
|
||||||
|
msgid "Never waits. Right when your company has no MFA."
|
||||||
|
msgstr "Nigdy nie czeka. Właściwe, gdy firma nie używa MFA."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:270
|
||||||
|
msgid ""
|
||||||
|
"Many servers with MFA need a second sign-in, and its prompt only comes after "
|
||||||
|
"a short wait. Automatic waits when the first sign-in took a while."
|
||||||
|
msgstr ""
|
||||||
|
"Wiele serwerów z MFA wymaga drugiego logowania, a jego żądanie przychodzi "
|
||||||
|
"dopiero po chwili. Automatycznie czeka, gdy pierwsze logowanie trochę trwało."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:273
|
||||||
|
msgid ""
|
||||||
|
"Loads the list of apps again once a day, so new apps show up by themselves."
|
||||||
|
msgstr ""
|
||||||
|
"Wczytuje listę aplikacji raz dziennie, więc nowe aplikacje pojawiają się "
|
||||||
|
"same."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:275 src/lib/menu.sh:330
|
||||||
|
msgid "Needs a systemd user session."
|
||||||
|
msgstr "Wymaga sesji użytkownika systemd."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:322 src/lib/menu.sh:325
|
||||||
|
msgid "Could not save the setting."
|
||||||
|
msgstr "Nie udało się zapisać ustawienia."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:371 src/lib/menu.sh:609
|
||||||
|
msgid "Settings"
|
||||||
|
msgstr "Ustawienia"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:372
|
||||||
|
msgid "↑↓ select ←→ or Space: change q: back"
|
||||||
|
msgstr "↑↓ wybierz ←→ lub Spacja: zmień q: wstecz"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:373
|
||||||
|
msgid "ON"
|
||||||
|
msgstr "WŁ."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:374
|
||||||
|
msgid "OFF"
|
||||||
|
msgstr "WYŁ."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:456 src/lib/menu.sh:608
|
||||||
|
msgid "Workspaces"
|
||||||
|
msgstr "Obszary robocze"
|
||||||
|
|
||||||
|
#. TRANSLATORS: keep the letters, they are the keys.
|
||||||
|
#: src/lib/menu.sh:458
|
||||||
|
msgid ""
|
||||||
|
"↑↓ select r: refresh s: sign in again d: remove a: add q: back"
|
||||||
|
msgstr ""
|
||||||
|
"↑↓ wybierz r: odśwież s: zaloguj ponownie d: usuń a: dodaj q: "
|
||||||
|
"wstecz"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:459
|
||||||
|
msgid "No workspace yet. Press a to add one."
|
||||||
|
msgstr "Nie ma jeszcze obszaru roboczego. Naciśnij a, aby dodać."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:484
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "refreshed %s"
|
||||||
|
msgstr "odświeżono %s"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:512
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Remove \"%s\" and its apps?"
|
||||||
|
msgstr "Usunąć „%s” i jego aplikacje?"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:536 src/lib/menu.sh:607
|
||||||
|
msgid "Start an app"
|
||||||
|
msgstr "Uruchom aplikację"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:537
|
||||||
|
msgid "↑↓ select Enter: start q: back"
|
||||||
|
msgstr "↑↓ wybierz Enter: uruchom q: wstecz"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:538
|
||||||
|
msgid "No apps yet. Add a workspace first."
|
||||||
|
msgstr "Nie ma jeszcze aplikacji. Najpierw dodaj obszar roboczy."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:579
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s is starting. It opens in a window of its own."
|
||||||
|
msgstr "Uruchamianie: %s. Otworzy się we własnym oknie."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:606
|
||||||
|
msgid "Add a workspace"
|
||||||
|
msgstr "Dodaj obszar roboczy"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:610
|
||||||
|
msgid "Quit"
|
||||||
|
msgstr "Zakończ"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:163
|
||||||
|
msgid "The server did not accept the user name or password."
|
||||||
|
msgstr "Serwer nie przyjął nazwy użytkownika lub hasła."
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:164
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not reach the server: %s"
|
||||||
|
msgstr "Nie udało się połączyć z serwerem: %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:165
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "There is no RD Web feed at %s."
|
||||||
|
msgstr "Pod adresem %s nie ma kanału RD Web."
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:166
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not load the apps: %s"
|
||||||
|
msgstr "Nie udało się wczytać aplikacji: %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:282
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Remote desktop of %s"
|
||||||
|
msgstr "Pulpit zdalny %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:284
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s from %s"
|
||||||
|
msgstr "%s z %s"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Apps"
|
||||||
|
msgstr "Aplikacje"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Share a folder"
|
||||||
|
msgstr "Udostępnij folder"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Open files with the apps"
|
||||||
|
msgstr "Otwieraj pliki w aplikacjach"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Size"
|
||||||
|
msgstr "Rozmiar"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Sound"
|
||||||
|
msgstr "Dźwięk"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Graphics"
|
||||||
|
msgstr "Grafika"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Signing in"
|
||||||
|
msgstr "Logowanie"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Wait for the second MFA prompt"
|
||||||
|
msgstr "Czekaj na drugie żądanie MFA"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Refresh the apps every day"
|
||||||
|
msgstr "Odświeżaj aplikacje codziennie"
|
||||||
+561
@@ -0,0 +1,561 @@
|
|||||||
|
# Brazilian Portuguese translation for rdfeed.
|
||||||
|
# Copyright (C) 2026 Felitendo
|
||||||
|
# This file is distributed under the same license as rdfeed.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: rdfeed 1.0.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: https://github.com/LoonixTools/rdfeed/issues\n"
|
||||||
|
"POT-Creation-Date: 2026-10-02 10:29+0200\n"
|
||||||
|
"PO-Revision-Date: 2026-10-02 09:30+0200\n"
|
||||||
|
"Last-Translator: Felitendo\n"
|
||||||
|
"Language-Team: Brazilian Portuguese\n"
|
||||||
|
"Language: pt_BR\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: src/rdfeed:79 src/rdfeed:103
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not write to %s."
|
||||||
|
msgstr "Não foi possível gravar em %s."
|
||||||
|
|
||||||
|
#: src/rdfeed:82
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Signing in to %s…"
|
||||||
|
msgstr "Entrando em %s…"
|
||||||
|
|
||||||
|
#: src/rdfeed:100
|
||||||
|
msgid ""
|
||||||
|
"The password could not be saved in the keyring, so each app asks for it."
|
||||||
|
msgstr ""
|
||||||
|
"Não foi possível salvar a senha no chaveiro, então cada app vai pedi-la."
|
||||||
|
|
||||||
|
#: src/rdfeed:111
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "1 app from %s is now in your app menu."
|
||||||
|
msgstr "1 app de %s agora está no seu menu de aplicativos."
|
||||||
|
|
||||||
|
#: src/rdfeed:113
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d apps from %s are now in your app menu."
|
||||||
|
msgstr "%d apps de %s agora estão no seu menu de aplicativos."
|
||||||
|
|
||||||
|
#: src/rdfeed:129
|
||||||
|
msgid ""
|
||||||
|
"Your IT department has the address. A work e-mail address often works too."
|
||||||
|
msgstr ""
|
||||||
|
"O seu departamento de TI tem o endereço. Muitas vezes o e-mail do trabalho "
|
||||||
|
"também funciona."
|
||||||
|
|
||||||
|
#: src/rdfeed:130
|
||||||
|
msgid "Feed address, server or e-mail:"
|
||||||
|
msgstr "Endereço do feed, servidor ou e-mail:"
|
||||||
|
|
||||||
|
#: src/rdfeed:137
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No feed is registered for %s. Ask your IT department for the address."
|
||||||
|
msgstr ""
|
||||||
|
"Nenhum feed está registrado para %s. Peça o endereço ao seu departamento de "
|
||||||
|
"TI."
|
||||||
|
|
||||||
|
#: src/rdfeed:142 src/rdfeed:156
|
||||||
|
msgid "User name (DOMAIN\\name or name@domain):"
|
||||||
|
msgstr "Nome de usuário (DOMÍNIO\\nome ou nome@domínio):"
|
||||||
|
|
||||||
|
#: src/rdfeed:146 src/rdfeed:159
|
||||||
|
msgid "Password:"
|
||||||
|
msgstr "Senha:"
|
||||||
|
|
||||||
|
#: src/rdfeed:178 src/rdfeed:236
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No workspace yet. Add one with `%s add`."
|
||||||
|
msgstr "Ainda não há espaço de trabalho. Adicione um com `%s add`."
|
||||||
|
|
||||||
|
#: src/rdfeed:187 src/lib/launch.sh:220
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No password saved for %s. Sign in again in rdfeed."
|
||||||
|
msgstr "Nenhuma senha salva para %s. Entre de novo no rdfeed."
|
||||||
|
|
||||||
|
#: src/rdfeed:191 src/lib/launch.sh:149
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Password for %s:"
|
||||||
|
msgstr "Senha de %s:"
|
||||||
|
|
||||||
|
#: src/rdfeed:195
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Refreshing %s…"
|
||||||
|
msgstr "Atualizando %s…"
|
||||||
|
|
||||||
|
#: src/rdfeed:205
|
||||||
|
msgid "The password no longer works. Sign in again in rdfeed."
|
||||||
|
msgstr "A senha não funciona mais. Entre de novo no rdfeed."
|
||||||
|
|
||||||
|
#: src/rdfeed:214
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s: %d apps"
|
||||||
|
msgstr "%s: %d apps"
|
||||||
|
|
||||||
|
#: src/rdfeed:222
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Which workspace? See `%s list` for the names."
|
||||||
|
msgstr "Qual espaço de trabalho? Os nomes estão em `%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:229
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s and its apps are removed."
|
||||||
|
msgstr "%s e os seus apps foram removidos."
|
||||||
|
|
||||||
|
#: src/rdfeed:253
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Which app? See `%s list`."
|
||||||
|
msgstr "Qual app? Veja `%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:268 src/lib/launch.sh:199
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "There is no app %s. See `%s list`."
|
||||||
|
msgstr "Não existe o app %s. Veja `%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:270
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Several workspaces have %s. Write it as WORKSPACE/APP, see `%s list`."
|
||||||
|
msgstr ""
|
||||||
|
"Vários espaços de trabalho têm %s. Escreva como ESPAÇO/APP, veja `%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:278
|
||||||
|
msgid "Usage: rdfeed [command]"
|
||||||
|
msgstr "Uso: rdfeed [comando]"
|
||||||
|
|
||||||
|
#: src/rdfeed:280
|
||||||
|
msgid "Commands:"
|
||||||
|
msgstr "Comandos:"
|
||||||
|
|
||||||
|
#: src/rdfeed:281
|
||||||
|
msgid "Add the workspace of your company"
|
||||||
|
msgstr "Adicionar o espaço de trabalho da sua empresa"
|
||||||
|
|
||||||
|
#: src/rdfeed:282
|
||||||
|
msgid "Load the apps again"
|
||||||
|
msgstr "Carregar os apps de novo"
|
||||||
|
|
||||||
|
#: src/rdfeed:283
|
||||||
|
msgid "List the workspaces and their apps"
|
||||||
|
msgstr "Listar os espaços de trabalho e seus apps"
|
||||||
|
|
||||||
|
#: src/rdfeed:284
|
||||||
|
msgid "Start an app, or open a file with it"
|
||||||
|
msgstr "Abrir um app, ou um arquivo com ele"
|
||||||
|
|
||||||
|
#: src/rdfeed:285
|
||||||
|
msgid "Remove a workspace and its apps"
|
||||||
|
msgstr "Remover um espaço de trabalho e seus apps"
|
||||||
|
|
||||||
|
#: src/rdfeed:286
|
||||||
|
msgid "Show this help"
|
||||||
|
msgstr "Mostrar esta ajuda"
|
||||||
|
|
||||||
|
#: src/rdfeed:287
|
||||||
|
msgid "Show the version"
|
||||||
|
msgstr "Mostrar a versão"
|
||||||
|
|
||||||
|
#: src/rdfeed:289
|
||||||
|
msgid "Without a command an interactive menu is shown."
|
||||||
|
msgstr "Sem comando, um menu interativo é exibido."
|
||||||
|
|
||||||
|
#: src/rdfeed:313
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Unknown command: %s"
|
||||||
|
msgstr "Comando desconhecido: %s"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:174 src/lib/menu.sh:251
|
||||||
|
msgid "never"
|
||||||
|
msgstr "nunca"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:184
|
||||||
|
msgid "just now"
|
||||||
|
msgstr "agora mesmo"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:186
|
||||||
|
msgid "1 minute ago"
|
||||||
|
msgstr "há 1 minuto"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:189
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d minutes ago"
|
||||||
|
msgstr "há %d minutos"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:191
|
||||||
|
msgid "1 hour ago"
|
||||||
|
msgstr "há 1 hora"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:194
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d hours ago"
|
||||||
|
msgstr "há %d horas"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:196
|
||||||
|
msgid "1 day ago"
|
||||||
|
msgstr "há 1 dia"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:199
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d days ago"
|
||||||
|
msgstr "há %d dias"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:169
|
||||||
|
msgid ""
|
||||||
|
"Your password has expired. Change it on Windows or on the RD Web page, then "
|
||||||
|
"sign in again in rdfeed."
|
||||||
|
msgstr ""
|
||||||
|
"A sua senha expirou. Mude-a no Windows ou na página do RD Web e depois entre "
|
||||||
|
"de novo no rdfeed."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:171
|
||||||
|
msgid "Your account is locked. Ask your IT department."
|
||||||
|
msgstr "A sua conta está bloqueada. Fale com o seu departamento de TI."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:173
|
||||||
|
msgid "The server did not accept the password. Sign in again in rdfeed."
|
||||||
|
msgstr "O servidor não aceitou a senha. Entre de novo no rdfeed."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:175
|
||||||
|
msgid "Could not reach the server. Check the internet connection."
|
||||||
|
msgstr ""
|
||||||
|
"Não foi possível alcançar o servidor. Verifique a conexão com a internet."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:178
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "FreeRDP stopped: %s"
|
||||||
|
msgstr "O FreeRDP parou: %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:205 src/lib/launch.sh:206
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "File not found: %s"
|
||||||
|
msgstr "Arquivo não encontrado: %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:212 src/lib/launch.sh:213
|
||||||
|
msgid ""
|
||||||
|
"FreeRDP 3 is missing. Install it to start the apps (the package is called "
|
||||||
|
"freerdp)."
|
||||||
|
msgstr ""
|
||||||
|
"O FreeRDP 3 não está instalado. Instale-o para abrir os apps (o pacote se "
|
||||||
|
"chama freerdp)."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:277
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Opening %s in %s"
|
||||||
|
msgstr "Abrindo %s no %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:278
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "If it does not open by itself, open it in the app from the drive %s."
|
||||||
|
msgstr "Se não abrir sozinho, abra-o no app pela unidade %s."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:280
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Starting %s"
|
||||||
|
msgstr "Abrindo %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:281
|
||||||
|
msgid ""
|
||||||
|
"If your company uses MFA, confirm the sign-in in your authenticator app."
|
||||||
|
msgstr "Se a sua empresa usa MFA, confirme a entrada no seu app autenticador."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:290
|
||||||
|
msgid "A second sign-in is coming"
|
||||||
|
msgstr "Vem uma segunda entrada"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:291
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid ""
|
||||||
|
"Confirm the second prompt in your authenticator app too. It comes in about "
|
||||||
|
"%d seconds."
|
||||||
|
msgstr ""
|
||||||
|
"Confirme também o segundo pedido no seu app autenticador. Ele chega em cerca "
|
||||||
|
"de %d segundos."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:301
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s could not start"
|
||||||
|
msgstr "Não foi possível abrir %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:303
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Log: %s"
|
||||||
|
msgstr "Log: %s"
|
||||||
|
|
||||||
|
#. TRANSLATORS: the letter after "[" is the key for yes. y works too.
|
||||||
|
#: src/lib/menu.sh:93
|
||||||
|
msgid "[y/N]"
|
||||||
|
msgstr "[s/N]"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:163
|
||||||
|
msgid "1 app"
|
||||||
|
msgstr "1 app"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:165
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d apps"
|
||||||
|
msgstr "%d apps"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:190
|
||||||
|
msgid "No workspace yet. Add the one of your company with [1]."
|
||||||
|
msgstr "Ainda não há espaço de trabalho. Adicione o da sua empresa com [1]."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:233
|
||||||
|
msgid "off"
|
||||||
|
msgstr "desativado"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:234
|
||||||
|
msgid "home folder"
|
||||||
|
msgstr "pasta pessoal"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:237
|
||||||
|
msgid "Documents"
|
||||||
|
msgstr "Documentos"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:238
|
||||||
|
msgid "Downloads"
|
||||||
|
msgstr "Downloads"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:243
|
||||||
|
msgid "not found"
|
||||||
|
msgstr "não encontrada"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:246
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "automatic (%d %%)"
|
||||||
|
msgstr "automático (%d %%)"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:248
|
||||||
|
msgid "classic"
|
||||||
|
msgstr "clássico"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:249
|
||||||
|
msgid "GFX"
|
||||||
|
msgstr "GFX"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:250
|
||||||
|
msgid "always"
|
||||||
|
msgstr "sempre"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:252
|
||||||
|
msgid "automatic"
|
||||||
|
msgstr "automático"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:261
|
||||||
|
msgid ""
|
||||||
|
"The apps only see the files on the server. Pick a folder to open and save "
|
||||||
|
"files of this computer in them."
|
||||||
|
msgstr ""
|
||||||
|
"Os apps só veem os arquivos do servidor. Escolha uma pasta para que possam "
|
||||||
|
"abrir e salvar arquivos deste computador."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:262
|
||||||
|
msgid ""
|
||||||
|
"The apps can open and save files in this folder. It shows up as a drive in "
|
||||||
|
"them."
|
||||||
|
msgstr ""
|
||||||
|
"Os apps podem abrir e salvar arquivos nesta pasta. Ela aparece neles como "
|
||||||
|
"uma unidade."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:263
|
||||||
|
msgid ""
|
||||||
|
"Offers the apps under \"Open with\" for their file types, for example Word "
|
||||||
|
"for .docx. The folder of the file is shared with the app while it is open."
|
||||||
|
msgstr ""
|
||||||
|
"Oferece os apps em \"Abrir com\" para seus tipos de arquivo, por exemplo o "
|
||||||
|
"Word para .docx. A pasta do arquivo fica compartilhada enquanto ele está "
|
||||||
|
"aberto."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:264
|
||||||
|
msgid ""
|
||||||
|
"How big the apps are drawn. Automatic follows the scale of your desktop, so "
|
||||||
|
"they are sharp and the right size."
|
||||||
|
msgstr ""
|
||||||
|
"O tamanho em que os apps são desenhados. Automático segue a escala da sua "
|
||||||
|
"área de trabalho, assim ficam nítidos e do tamanho certo."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:265
|
||||||
|
msgid "Plays the sound of the apps here, for calls and videos."
|
||||||
|
msgstr "Toca aqui o som dos apps, para chamadas e vídeos."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:266
|
||||||
|
msgid ""
|
||||||
|
"Paints every window right. But some servers end the session right after "
|
||||||
|
"signing in, then go back to GFX."
|
||||||
|
msgstr "Desenha cada janela direito. Mas alguns servidores encerram a sessão logo após a entrada; nesse caso, volte ao GFX."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:267
|
||||||
|
msgid ""
|
||||||
|
"Works with every server. FreeRDP may not repaint a window after it was "
|
||||||
|
"minimized or resized."
|
||||||
|
msgstr "Funciona com qualquer servidor. O FreeRDP pode não redesenhar uma janela depois de minimizada ou redimensionada."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:268
|
||||||
|
msgid ""
|
||||||
|
"Always waits before the second sign-in. For when the second prompt does not "
|
||||||
|
"come with automatic."
|
||||||
|
msgstr ""
|
||||||
|
"Sempre espera antes da segunda entrada. Para quando, no automático, o "
|
||||||
|
"segundo pedido não chega."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:269
|
||||||
|
msgid "Never waits. Right when your company has no MFA."
|
||||||
|
msgstr "Nunca espera. Certo quando a sua empresa não usa MFA."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:270
|
||||||
|
msgid ""
|
||||||
|
"Many servers with MFA need a second sign-in, and its prompt only comes after "
|
||||||
|
"a short wait. Automatic waits when the first sign-in took a while."
|
||||||
|
msgstr ""
|
||||||
|
"Muitos servidores com MFA pedem uma segunda entrada, e o pedido só chega "
|
||||||
|
"depois de uma breve espera. Automático espera quando a primeira entrada "
|
||||||
|
"demorou."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:273
|
||||||
|
msgid ""
|
||||||
|
"Loads the list of apps again once a day, so new apps show up by themselves."
|
||||||
|
msgstr ""
|
||||||
|
"Carrega a lista de apps de novo uma vez por dia, assim os novos aparecem "
|
||||||
|
"sozinhos."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:275 src/lib/menu.sh:330
|
||||||
|
msgid "Needs a systemd user session."
|
||||||
|
msgstr "Precisa de uma sessão de usuário do systemd."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:322 src/lib/menu.sh:325
|
||||||
|
msgid "Could not save the setting."
|
||||||
|
msgstr "Não foi possível salvar a configuração."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:371 src/lib/menu.sh:609
|
||||||
|
msgid "Settings"
|
||||||
|
msgstr "Configurações"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:372
|
||||||
|
msgid "↑↓ select ←→ or Space: change q: back"
|
||||||
|
msgstr "↑↓ escolher ←→ ou Espaço: mudar q: voltar"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:373
|
||||||
|
msgid "ON"
|
||||||
|
msgstr "ATIVADO"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:374
|
||||||
|
msgid "OFF"
|
||||||
|
msgstr "DESATIVADO"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:456 src/lib/menu.sh:608
|
||||||
|
msgid "Workspaces"
|
||||||
|
msgstr "Espaços de trabalho"
|
||||||
|
|
||||||
|
#. TRANSLATORS: keep the letters, they are the keys.
|
||||||
|
#: src/lib/menu.sh:458
|
||||||
|
msgid ""
|
||||||
|
"↑↓ select r: refresh s: sign in again d: remove a: add q: back"
|
||||||
|
msgstr ""
|
||||||
|
"↑↓ escolher r: atualizar s: entrar de novo d: remover a: adicionar "
|
||||||
|
"q: voltar"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:459
|
||||||
|
msgid "No workspace yet. Press a to add one."
|
||||||
|
msgstr "Ainda não há espaço de trabalho. Aperte a para adicionar um."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:484
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "refreshed %s"
|
||||||
|
msgstr "atualizado %s"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:512
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Remove \"%s\" and its apps?"
|
||||||
|
msgstr "Remover \"%s\" e seus apps?"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:536 src/lib/menu.sh:607
|
||||||
|
msgid "Start an app"
|
||||||
|
msgstr "Abrir um app"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:537
|
||||||
|
msgid "↑↓ select Enter: start q: back"
|
||||||
|
msgstr "↑↓ escolher Enter: abrir q: voltar"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:538
|
||||||
|
msgid "No apps yet. Add a workspace first."
|
||||||
|
msgstr "Ainda não há apps. Adicione primeiro um espaço de trabalho."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:579
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s is starting. It opens in a window of its own."
|
||||||
|
msgstr "%s está abrindo. Ele abre numa janela própria."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:606
|
||||||
|
msgid "Add a workspace"
|
||||||
|
msgstr "Adicionar um espaço de trabalho"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:610
|
||||||
|
msgid "Quit"
|
||||||
|
msgstr "Sair"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:163
|
||||||
|
msgid "The server did not accept the user name or password."
|
||||||
|
msgstr "O servidor não aceitou o nome de usuário ou a senha."
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:164
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not reach the server: %s"
|
||||||
|
msgstr "Não foi possível alcançar o servidor: %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:165
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "There is no RD Web feed at %s."
|
||||||
|
msgstr "Não há feed do RD Web em %s."
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:166
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not load the apps: %s"
|
||||||
|
msgstr "Não foi possível carregar os apps: %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:282
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Remote desktop of %s"
|
||||||
|
msgstr "Área de trabalho remota de %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:284
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s from %s"
|
||||||
|
msgstr "%s de %s"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Apps"
|
||||||
|
msgstr "Apps"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Share a folder"
|
||||||
|
msgstr "Compartilhar uma pasta"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Open files with the apps"
|
||||||
|
msgstr "Abrir arquivos com os apps"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Size"
|
||||||
|
msgstr "Tamanho"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Sound"
|
||||||
|
msgstr "Som"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Graphics"
|
||||||
|
msgstr "Gráficos"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Signing in"
|
||||||
|
msgstr "Acesso"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Wait for the second MFA prompt"
|
||||||
|
msgstr "Esperar o segundo pedido de MFA"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Refresh the apps every day"
|
||||||
|
msgstr "Atualizar os apps todo dia"
|
||||||
+531
@@ -0,0 +1,531 @@
|
|||||||
|
# Translation template for rdfeed.
|
||||||
|
# Copyright (C) 2026 Felitendo
|
||||||
|
# This file is distributed under the same license as rdfeed.
|
||||||
|
#
|
||||||
|
#, fuzzy
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: rdfeed 1.0.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: https://github.com/LoonixTools/rdfeed/issues\n"
|
||||||
|
"POT-Creation-Date: 2026-10-02 10:29+0200\n"
|
||||||
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
"Language: \n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: src/rdfeed:79 src/rdfeed:103
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not write to %s."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/rdfeed:82
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Signing in to %s…"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/rdfeed:100
|
||||||
|
msgid ""
|
||||||
|
"The password could not be saved in the keyring, so each app asks for it."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/rdfeed:111
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "1 app from %s is now in your app menu."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/rdfeed:113
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d apps from %s are now in your app menu."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/rdfeed:129
|
||||||
|
msgid ""
|
||||||
|
"Your IT department has the address. A work e-mail address often works too."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/rdfeed:130
|
||||||
|
msgid "Feed address, server or e-mail:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/rdfeed:137
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No feed is registered for %s. Ask your IT department for the address."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/rdfeed:142 src/rdfeed:156
|
||||||
|
msgid "User name (DOMAIN\\name or name@domain):"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/rdfeed:146 src/rdfeed:159
|
||||||
|
msgid "Password:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/rdfeed:178 src/rdfeed:236
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No workspace yet. Add one with `%s add`."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/rdfeed:187 src/lib/launch.sh:220
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No password saved for %s. Sign in again in rdfeed."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/rdfeed:191 src/lib/launch.sh:149
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Password for %s:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/rdfeed:195
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Refreshing %s…"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/rdfeed:205
|
||||||
|
msgid "The password no longer works. Sign in again in rdfeed."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/rdfeed:214
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s: %d apps"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/rdfeed:222
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Which workspace? See `%s list` for the names."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/rdfeed:229
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s and its apps are removed."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/rdfeed:253
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Which app? See `%s list`."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/rdfeed:268 src/lib/launch.sh:199
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "There is no app %s. See `%s list`."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/rdfeed:270
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Several workspaces have %s. Write it as WORKSPACE/APP, see `%s list`."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/rdfeed:278
|
||||||
|
msgid "Usage: rdfeed [command]"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/rdfeed:280
|
||||||
|
msgid "Commands:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/rdfeed:281
|
||||||
|
msgid "Add the workspace of your company"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/rdfeed:282
|
||||||
|
msgid "Load the apps again"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/rdfeed:283
|
||||||
|
msgid "List the workspaces and their apps"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/rdfeed:284
|
||||||
|
msgid "Start an app, or open a file with it"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/rdfeed:285
|
||||||
|
msgid "Remove a workspace and its apps"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/rdfeed:286
|
||||||
|
msgid "Show this help"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/rdfeed:287
|
||||||
|
msgid "Show the version"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/rdfeed:289
|
||||||
|
msgid "Without a command an interactive menu is shown."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/rdfeed:313
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Unknown command: %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/common.sh:174 src/lib/menu.sh:251
|
||||||
|
msgid "never"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/common.sh:184
|
||||||
|
msgid "just now"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/common.sh:186
|
||||||
|
msgid "1 minute ago"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/common.sh:189
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d minutes ago"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/common.sh:191
|
||||||
|
msgid "1 hour ago"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/common.sh:194
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d hours ago"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/common.sh:196
|
||||||
|
msgid "1 day ago"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/common.sh:199
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d days ago"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:169
|
||||||
|
msgid ""
|
||||||
|
"Your password has expired. Change it on Windows or on the RD Web page, then "
|
||||||
|
"sign in again in rdfeed."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:171
|
||||||
|
msgid "Your account is locked. Ask your IT department."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:173
|
||||||
|
msgid "The server did not accept the password. Sign in again in rdfeed."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:175
|
||||||
|
msgid "Could not reach the server. Check the internet connection."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:178
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "FreeRDP stopped: %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:205 src/lib/launch.sh:206
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "File not found: %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:212 src/lib/launch.sh:213
|
||||||
|
msgid ""
|
||||||
|
"FreeRDP 3 is missing. Install it to start the apps (the package is called "
|
||||||
|
"freerdp)."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:277
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Opening %s in %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:278
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "If it does not open by itself, open it in the app from the drive %s."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:280
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Starting %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:281
|
||||||
|
msgid ""
|
||||||
|
"If your company uses MFA, confirm the sign-in in your authenticator app."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:290
|
||||||
|
msgid "A second sign-in is coming"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:291
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid ""
|
||||||
|
"Confirm the second prompt in your authenticator app too. It comes in about "
|
||||||
|
"%d seconds."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:301
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s could not start"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:303
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Log: %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. TRANSLATORS: the letter after "[" is the key for yes. y works too.
|
||||||
|
#: src/lib/menu.sh:93
|
||||||
|
msgid "[y/N]"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:163
|
||||||
|
msgid "1 app"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:165
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d apps"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:190
|
||||||
|
msgid "No workspace yet. Add the one of your company with [1]."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:233
|
||||||
|
msgid "off"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:234
|
||||||
|
msgid "home folder"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:237
|
||||||
|
msgid "Documents"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:238
|
||||||
|
msgid "Downloads"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:243
|
||||||
|
msgid "not found"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:246
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "automatic (%d %%)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:248
|
||||||
|
msgid "classic"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:249
|
||||||
|
msgid "GFX"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:250
|
||||||
|
msgid "always"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:252
|
||||||
|
msgid "automatic"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:261
|
||||||
|
msgid ""
|
||||||
|
"The apps only see the files on the server. Pick a folder to open and save "
|
||||||
|
"files of this computer in them."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:262
|
||||||
|
msgid ""
|
||||||
|
"The apps can open and save files in this folder. It shows up as a drive in "
|
||||||
|
"them."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:263
|
||||||
|
msgid ""
|
||||||
|
"Offers the apps under \"Open with\" for their file types, for example Word "
|
||||||
|
"for .docx. The folder of the file is shared with the app while it is open."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:264
|
||||||
|
msgid ""
|
||||||
|
"How big the apps are drawn. Automatic follows the scale of your desktop, so "
|
||||||
|
"they are sharp and the right size."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:265
|
||||||
|
msgid "Plays the sound of the apps here, for calls and videos."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:266
|
||||||
|
msgid ""
|
||||||
|
"Paints every window right. But some servers end the session right after "
|
||||||
|
"signing in, then go back to GFX."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:267
|
||||||
|
msgid ""
|
||||||
|
"Works with every server. FreeRDP may not repaint a window after it was "
|
||||||
|
"minimized or resized."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:268
|
||||||
|
msgid ""
|
||||||
|
"Always waits before the second sign-in. For when the second prompt does not "
|
||||||
|
"come with automatic."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:269
|
||||||
|
msgid "Never waits. Right when your company has no MFA."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:270
|
||||||
|
msgid ""
|
||||||
|
"Many servers with MFA need a second sign-in, and its prompt only comes after "
|
||||||
|
"a short wait. Automatic waits when the first sign-in took a while."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:273
|
||||||
|
msgid ""
|
||||||
|
"Loads the list of apps again once a day, so new apps show up by themselves."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:275 src/lib/menu.sh:330
|
||||||
|
msgid "Needs a systemd user session."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:322 src/lib/menu.sh:325
|
||||||
|
msgid "Could not save the setting."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:371 src/lib/menu.sh:609
|
||||||
|
msgid "Settings"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:372
|
||||||
|
msgid "↑↓ select ←→ or Space: change q: back"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:373
|
||||||
|
msgid "ON"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:374
|
||||||
|
msgid "OFF"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:456 src/lib/menu.sh:608
|
||||||
|
msgid "Workspaces"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. TRANSLATORS: keep the letters, they are the keys.
|
||||||
|
#: src/lib/menu.sh:458
|
||||||
|
msgid ""
|
||||||
|
"↑↓ select r: refresh s: sign in again d: remove a: add q: back"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:459
|
||||||
|
msgid "No workspace yet. Press a to add one."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:484
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "refreshed %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:512
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Remove \"%s\" and its apps?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:536 src/lib/menu.sh:607
|
||||||
|
msgid "Start an app"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:537
|
||||||
|
msgid "↑↓ select Enter: start q: back"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:538
|
||||||
|
msgid "No apps yet. Add a workspace first."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:579
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s is starting. It opens in a window of its own."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:606
|
||||||
|
msgid "Add a workspace"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:610
|
||||||
|
msgid "Quit"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:163
|
||||||
|
msgid "The server did not accept the user name or password."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:164
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not reach the server: %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:165
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "There is no RD Web feed at %s."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:166
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not load the apps: %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:282
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Remote desktop of %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:284
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s from %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Apps"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Share a folder"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Open files with the apps"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Size"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Sound"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Graphics"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Signing in"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Wait for the second MFA prompt"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Refresh the apps every day"
|
||||||
|
msgstr ""
|
||||||
@@ -0,0 +1,558 @@
|
|||||||
|
# Russian translation for rdfeed.
|
||||||
|
# Copyright (C) 2026 Felitendo
|
||||||
|
# This file is distributed under the same license as rdfeed.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: rdfeed 1.0.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: https://github.com/LoonixTools/rdfeed/issues\n"
|
||||||
|
"POT-Creation-Date: 2026-10-02 10:29+0200\n"
|
||||||
|
"PO-Revision-Date: 2026-10-02 09:30+0200\n"
|
||||||
|
"Last-Translator: Felitendo\n"
|
||||||
|
"Language-Team: Russian\n"
|
||||||
|
"Language: ru\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: src/rdfeed:79 src/rdfeed:103
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not write to %s."
|
||||||
|
msgstr "Не удалось записать в %s."
|
||||||
|
|
||||||
|
#: src/rdfeed:82
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Signing in to %s…"
|
||||||
|
msgstr "Вход на %s…"
|
||||||
|
|
||||||
|
#: src/rdfeed:100
|
||||||
|
msgid ""
|
||||||
|
"The password could not be saved in the keyring, so each app asks for it."
|
||||||
|
msgstr ""
|
||||||
|
"Не удалось сохранить пароль в связке ключей, поэтому каждое приложение будет "
|
||||||
|
"его запрашивать."
|
||||||
|
|
||||||
|
#: src/rdfeed:111
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "1 app from %s is now in your app menu."
|
||||||
|
msgstr "1 приложение из %s теперь в меню приложений."
|
||||||
|
|
||||||
|
#: src/rdfeed:113
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d apps from %s are now in your app menu."
|
||||||
|
msgstr "Приложений: %d (из %s). Теперь они в меню приложений."
|
||||||
|
|
||||||
|
#: src/rdfeed:129
|
||||||
|
msgid ""
|
||||||
|
"Your IT department has the address. A work e-mail address often works too."
|
||||||
|
msgstr ""
|
||||||
|
"Адрес есть у вашего ИТ-отдела. Часто подходит и рабочий адрес электронной "
|
||||||
|
"почты."
|
||||||
|
|
||||||
|
#: src/rdfeed:130
|
||||||
|
msgid "Feed address, server or e-mail:"
|
||||||
|
msgstr "Адрес фида, сервер или e-mail:"
|
||||||
|
|
||||||
|
#: src/rdfeed:137
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No feed is registered for %s. Ask your IT department for the address."
|
||||||
|
msgstr "Для %s не зарегистрирован фид. Спросите адрес у ИТ-отдела."
|
||||||
|
|
||||||
|
#: src/rdfeed:142 src/rdfeed:156
|
||||||
|
msgid "User name (DOMAIN\\name or name@domain):"
|
||||||
|
msgstr "Имя пользователя (ДОМЕН\\имя или имя@домен):"
|
||||||
|
|
||||||
|
#: src/rdfeed:146 src/rdfeed:159
|
||||||
|
msgid "Password:"
|
||||||
|
msgstr "Пароль:"
|
||||||
|
|
||||||
|
#: src/rdfeed:178 src/rdfeed:236
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No workspace yet. Add one with `%s add`."
|
||||||
|
msgstr "Рабочих областей пока нет. Добавьте её командой `%s add`."
|
||||||
|
|
||||||
|
#: src/rdfeed:187 src/lib/launch.sh:220
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No password saved for %s. Sign in again in rdfeed."
|
||||||
|
msgstr "Для %s не сохранён пароль. Войдите снова в rdfeed."
|
||||||
|
|
||||||
|
#: src/rdfeed:191 src/lib/launch.sh:149
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Password for %s:"
|
||||||
|
msgstr "Пароль для %s:"
|
||||||
|
|
||||||
|
#: src/rdfeed:195
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Refreshing %s…"
|
||||||
|
msgstr "Обновление %s…"
|
||||||
|
|
||||||
|
#: src/rdfeed:205
|
||||||
|
msgid "The password no longer works. Sign in again in rdfeed."
|
||||||
|
msgstr "Пароль больше не подходит. Войдите снова в rdfeed."
|
||||||
|
|
||||||
|
#: src/rdfeed:214
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s: %d apps"
|
||||||
|
msgstr "%s: приложений: %d"
|
||||||
|
|
||||||
|
#: src/rdfeed:222
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Which workspace? See `%s list` for the names."
|
||||||
|
msgstr "Какая рабочая область? Названия показывает `%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:229
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s and its apps are removed."
|
||||||
|
msgstr "Удалено: %s вместе с приложениями."
|
||||||
|
|
||||||
|
#: src/rdfeed:253
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Which app? See `%s list`."
|
||||||
|
msgstr "Какое приложение? Смотрите `%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:268 src/lib/launch.sh:199
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "There is no app %s. See `%s list`."
|
||||||
|
msgstr "Приложения %s нет. Смотрите `%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:270
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Several workspaces have %s. Write it as WORKSPACE/APP, see `%s list`."
|
||||||
|
msgstr ""
|
||||||
|
"%s есть в нескольких рабочих областях. Укажите в виде ОБЛАСТЬ/ПРИЛОЖЕНИЕ, "
|
||||||
|
"смотрите `%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:278
|
||||||
|
msgid "Usage: rdfeed [command]"
|
||||||
|
msgstr "Использование: rdfeed [команда]"
|
||||||
|
|
||||||
|
#: src/rdfeed:280
|
||||||
|
msgid "Commands:"
|
||||||
|
msgstr "Команды:"
|
||||||
|
|
||||||
|
#: src/rdfeed:281
|
||||||
|
msgid "Add the workspace of your company"
|
||||||
|
msgstr "Добавить рабочую область вашей компании"
|
||||||
|
|
||||||
|
#: src/rdfeed:282
|
||||||
|
msgid "Load the apps again"
|
||||||
|
msgstr "Загрузить приложения заново"
|
||||||
|
|
||||||
|
#: src/rdfeed:283
|
||||||
|
msgid "List the workspaces and their apps"
|
||||||
|
msgstr "Показать рабочие области и их приложения"
|
||||||
|
|
||||||
|
#: src/rdfeed:284
|
||||||
|
msgid "Start an app, or open a file with it"
|
||||||
|
msgstr "Запустить приложение или открыть в нём файл"
|
||||||
|
|
||||||
|
#: src/rdfeed:285
|
||||||
|
msgid "Remove a workspace and its apps"
|
||||||
|
msgstr "Удалить рабочую область и её приложения"
|
||||||
|
|
||||||
|
#: src/rdfeed:286
|
||||||
|
msgid "Show this help"
|
||||||
|
msgstr "Показать эту справку"
|
||||||
|
|
||||||
|
#: src/rdfeed:287
|
||||||
|
msgid "Show the version"
|
||||||
|
msgstr "Показать версию"
|
||||||
|
|
||||||
|
#: src/rdfeed:289
|
||||||
|
msgid "Without a command an interactive menu is shown."
|
||||||
|
msgstr "Без команды открывается интерактивное меню."
|
||||||
|
|
||||||
|
#: src/rdfeed:313
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Unknown command: %s"
|
||||||
|
msgstr "Неизвестная команда: %s"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:174 src/lib/menu.sh:251
|
||||||
|
msgid "never"
|
||||||
|
msgstr "никогда"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:184
|
||||||
|
msgid "just now"
|
||||||
|
msgstr "только что"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:186
|
||||||
|
msgid "1 minute ago"
|
||||||
|
msgstr "1 минуту назад"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:189
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d minutes ago"
|
||||||
|
msgstr "%d мин назад"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:191
|
||||||
|
msgid "1 hour ago"
|
||||||
|
msgstr "1 час назад"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:194
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d hours ago"
|
||||||
|
msgstr "%d ч назад"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:196
|
||||||
|
msgid "1 day ago"
|
||||||
|
msgstr "1 день назад"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:199
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d days ago"
|
||||||
|
msgstr "%d дн. назад"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:169
|
||||||
|
msgid ""
|
||||||
|
"Your password has expired. Change it on Windows or on the RD Web page, then "
|
||||||
|
"sign in again in rdfeed."
|
||||||
|
msgstr ""
|
||||||
|
"Срок действия пароля истёк. Смените его в Windows или на странице RD Web, "
|
||||||
|
"затем снова войдите в rdfeed."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:171
|
||||||
|
msgid "Your account is locked. Ask your IT department."
|
||||||
|
msgstr "Учётная запись заблокирована. Обратитесь в ИТ-отдел."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:173
|
||||||
|
msgid "The server did not accept the password. Sign in again in rdfeed."
|
||||||
|
msgstr "Сервер не принял пароль. Войдите снова в rdfeed."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:175
|
||||||
|
msgid "Could not reach the server. Check the internet connection."
|
||||||
|
msgstr "Не удалось связаться с сервером. Проверьте подключение к интернету."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:178
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "FreeRDP stopped: %s"
|
||||||
|
msgstr "FreeRDP остановился: %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:205 src/lib/launch.sh:206
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "File not found: %s"
|
||||||
|
msgstr "Файл не найден: %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:212 src/lib/launch.sh:213
|
||||||
|
msgid ""
|
||||||
|
"FreeRDP 3 is missing. Install it to start the apps (the package is called "
|
||||||
|
"freerdp)."
|
||||||
|
msgstr ""
|
||||||
|
"Нет FreeRDP 3. Установите его, чтобы запускать приложения (пакет называется "
|
||||||
|
"freerdp)."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:277
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Opening %s in %s"
|
||||||
|
msgstr "Открытие %s в %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:278
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "If it does not open by itself, open it in the app from the drive %s."
|
||||||
|
msgstr "Если файл не откроется сам, откройте его в приложении с диска %s."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:280
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Starting %s"
|
||||||
|
msgstr "Запуск: %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:281
|
||||||
|
msgid ""
|
||||||
|
"If your company uses MFA, confirm the sign-in in your authenticator app."
|
||||||
|
msgstr ""
|
||||||
|
"Если ваша компания использует MFA, подтвердите вход в приложении-"
|
||||||
|
"аутентификаторе."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:290
|
||||||
|
msgid "A second sign-in is coming"
|
||||||
|
msgstr "Будет второй вход"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:291
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid ""
|
||||||
|
"Confirm the second prompt in your authenticator app too. It comes in about "
|
||||||
|
"%d seconds."
|
||||||
|
msgstr ""
|
||||||
|
"Подтвердите и второй запрос в приложении-аутентификаторе. Он придёт примерно "
|
||||||
|
"через %d с."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:301
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s could not start"
|
||||||
|
msgstr "Не удалось запустить: %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:303
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Log: %s"
|
||||||
|
msgstr "Журнал: %s"
|
||||||
|
|
||||||
|
#. TRANSLATORS: the letter after "[" is the key for yes. y works too.
|
||||||
|
#: src/lib/menu.sh:93
|
||||||
|
msgid "[y/N]"
|
||||||
|
msgstr "[д/Н]"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:163
|
||||||
|
msgid "1 app"
|
||||||
|
msgstr "1 приложение"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:165
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d apps"
|
||||||
|
msgstr "приложений: %d"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:190
|
||||||
|
msgid "No workspace yet. Add the one of your company with [1]."
|
||||||
|
msgstr "Рабочих областей пока нет. Добавьте область вашей компании через [1]."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:233
|
||||||
|
msgid "off"
|
||||||
|
msgstr "выкл."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:234
|
||||||
|
msgid "home folder"
|
||||||
|
msgstr "домашняя папка"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:237
|
||||||
|
msgid "Documents"
|
||||||
|
msgstr "Документы"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:238
|
||||||
|
msgid "Downloads"
|
||||||
|
msgstr "Загрузки"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:243
|
||||||
|
msgid "not found"
|
||||||
|
msgstr "не найдена"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:246
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "automatic (%d %%)"
|
||||||
|
msgstr "автоматически (%d %%)"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:248
|
||||||
|
msgid "classic"
|
||||||
|
msgstr "классический"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:249
|
||||||
|
msgid "GFX"
|
||||||
|
msgstr "GFX"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:250
|
||||||
|
msgid "always"
|
||||||
|
msgstr "всегда"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:252
|
||||||
|
msgid "automatic"
|
||||||
|
msgstr "автоматически"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:261
|
||||||
|
msgid ""
|
||||||
|
"The apps only see the files on the server. Pick a folder to open and save "
|
||||||
|
"files of this computer in them."
|
||||||
|
msgstr ""
|
||||||
|
"Приложения видят только файлы на сервере. Выберите папку, чтобы они могли "
|
||||||
|
"открывать и сохранять файлы этого компьютера."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:262
|
||||||
|
msgid ""
|
||||||
|
"The apps can open and save files in this folder. It shows up as a drive in "
|
||||||
|
"them."
|
||||||
|
msgstr ""
|
||||||
|
"Приложения могут открывать и сохранять файлы в этой папке. В них она видна "
|
||||||
|
"как диск."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:263
|
||||||
|
msgid ""
|
||||||
|
"Offers the apps under \"Open with\" for their file types, for example Word "
|
||||||
|
"for .docx. The folder of the file is shared with the app while it is open."
|
||||||
|
msgstr ""
|
||||||
|
"Предлагает приложения в «Открыть с помощью» для их типов файлов, например "
|
||||||
|
"Word для .docx. Папка файла открыта для сервера, пока файл открыт."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:264
|
||||||
|
msgid ""
|
||||||
|
"How big the apps are drawn. Automatic follows the scale of your desktop, so "
|
||||||
|
"they are sharp and the right size."
|
||||||
|
msgstr ""
|
||||||
|
"Насколько крупно рисуются приложения. «Автоматически» следует масштабу "
|
||||||
|
"рабочего стола, так они чёткие и нужного размера."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:265
|
||||||
|
msgid "Plays the sound of the apps here, for calls and videos."
|
||||||
|
msgstr "Воспроизводит звук приложений здесь, для звонков и видео."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:266
|
||||||
|
msgid ""
|
||||||
|
"Paints every window right. But some servers end the session right after "
|
||||||
|
"signing in, then go back to GFX."
|
||||||
|
msgstr "Правильно рисует каждое окно. Но некоторые серверы обрывают сеанс сразу после входа, тогда вернитесь к GFX."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:267
|
||||||
|
msgid ""
|
||||||
|
"Works with every server. FreeRDP may not repaint a window after it was "
|
||||||
|
"minimized or resized."
|
||||||
|
msgstr "Работает с любым сервером. FreeRDP может не перерисовать окно после сворачивания или изменения размера."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:268
|
||||||
|
msgid ""
|
||||||
|
"Always waits before the second sign-in. For when the second prompt does not "
|
||||||
|
"come with automatic."
|
||||||
|
msgstr ""
|
||||||
|
"Всегда ждёт перед вторым входом. Если с «автоматически» второй запрос не "
|
||||||
|
"приходит."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:269
|
||||||
|
msgid "Never waits. Right when your company has no MFA."
|
||||||
|
msgstr "Никогда не ждёт. Подходит, если ваша компания не использует MFA."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:270
|
||||||
|
msgid ""
|
||||||
|
"Many servers with MFA need a second sign-in, and its prompt only comes after "
|
||||||
|
"a short wait. Automatic waits when the first sign-in took a while."
|
||||||
|
msgstr ""
|
||||||
|
"Многим серверам с MFA нужен второй вход, и его запрос приходит только после "
|
||||||
|
"короткой паузы. «Автоматически» ждёт, если первый вход занял время."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:273
|
||||||
|
msgid ""
|
||||||
|
"Loads the list of apps again once a day, so new apps show up by themselves."
|
||||||
|
msgstr ""
|
||||||
|
"Раз в день заново загружает список приложений, так новые появляются сами."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:275 src/lib/menu.sh:330
|
||||||
|
msgid "Needs a systemd user session."
|
||||||
|
msgstr "Нужен пользовательский сеанс systemd."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:322 src/lib/menu.sh:325
|
||||||
|
msgid "Could not save the setting."
|
||||||
|
msgstr "Не удалось сохранить настройку."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:371 src/lib/menu.sh:609
|
||||||
|
msgid "Settings"
|
||||||
|
msgstr "Настройки"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:372
|
||||||
|
msgid "↑↓ select ←→ or Space: change q: back"
|
||||||
|
msgstr "↑↓ выбор ←→ или Пробел: изменить q: назад"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:373
|
||||||
|
msgid "ON"
|
||||||
|
msgstr "ВКЛ"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:374
|
||||||
|
msgid "OFF"
|
||||||
|
msgstr "ВЫКЛ"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:456 src/lib/menu.sh:608
|
||||||
|
msgid "Workspaces"
|
||||||
|
msgstr "Рабочие области"
|
||||||
|
|
||||||
|
#. TRANSLATORS: keep the letters, they are the keys.
|
||||||
|
#: src/lib/menu.sh:458
|
||||||
|
msgid ""
|
||||||
|
"↑↓ select r: refresh s: sign in again d: remove a: add q: back"
|
||||||
|
msgstr ""
|
||||||
|
"↑↓ выбор r: обновить s: войти снова d: удалить a: добавить q: назад"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:459
|
||||||
|
msgid "No workspace yet. Press a to add one."
|
||||||
|
msgstr "Рабочих областей пока нет. Нажмите a, чтобы добавить."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:484
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "refreshed %s"
|
||||||
|
msgstr "обновлено %s"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:512
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Remove \"%s\" and its apps?"
|
||||||
|
msgstr "Удалить «%s» вместе с приложениями?"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:536 src/lib/menu.sh:607
|
||||||
|
msgid "Start an app"
|
||||||
|
msgstr "Запустить приложение"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:537
|
||||||
|
msgid "↑↓ select Enter: start q: back"
|
||||||
|
msgstr "↑↓ выбор Enter: запустить q: назад"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:538
|
||||||
|
msgid "No apps yet. Add a workspace first."
|
||||||
|
msgstr "Приложений пока нет. Сначала добавьте рабочую область."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:579
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s is starting. It opens in a window of its own."
|
||||||
|
msgstr "Запуск: %s. Откроется в отдельном окне."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:606
|
||||||
|
msgid "Add a workspace"
|
||||||
|
msgstr "Добавить рабочую область"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:610
|
||||||
|
msgid "Quit"
|
||||||
|
msgstr "Выход"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:163
|
||||||
|
msgid "The server did not accept the user name or password."
|
||||||
|
msgstr "Сервер не принял имя пользователя или пароль."
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:164
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not reach the server: %s"
|
||||||
|
msgstr "Не удалось связаться с сервером: %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:165
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "There is no RD Web feed at %s."
|
||||||
|
msgstr "По адресу %s нет фида RD Web."
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:166
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not load the apps: %s"
|
||||||
|
msgstr "Не удалось загрузить приложения: %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:282
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Remote desktop of %s"
|
||||||
|
msgstr "Удалённый рабочий стол %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:284
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s from %s"
|
||||||
|
msgstr "%s из %s"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Apps"
|
||||||
|
msgstr "Приложения"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Share a folder"
|
||||||
|
msgstr "Общая папка"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Open files with the apps"
|
||||||
|
msgstr "Открывать файлы в приложениях"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Size"
|
||||||
|
msgstr "Размер"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Sound"
|
||||||
|
msgstr "Звук"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Graphics"
|
||||||
|
msgstr "Графика"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Signing in"
|
||||||
|
msgstr "Вход"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Wait for the second MFA prompt"
|
||||||
|
msgstr "Ждать второй запрос MFA"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Refresh the apps every day"
|
||||||
|
msgstr "Обновлять приложения каждый день"
|
||||||
@@ -0,0 +1,554 @@
|
|||||||
|
# Turkish translation for rdfeed.
|
||||||
|
# Copyright (C) 2026 Felitendo
|
||||||
|
# This file is distributed under the same license as rdfeed.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: rdfeed 1.0.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: https://github.com/LoonixTools/rdfeed/issues\n"
|
||||||
|
"POT-Creation-Date: 2026-10-02 10:29+0200\n"
|
||||||
|
"PO-Revision-Date: 2026-10-02 09:30+0200\n"
|
||||||
|
"Last-Translator: Felitendo\n"
|
||||||
|
"Language-Team: Turkish\n"
|
||||||
|
"Language: tr\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: src/rdfeed:79 src/rdfeed:103
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not write to %s."
|
||||||
|
msgstr "%s konumuna yazılamadı."
|
||||||
|
|
||||||
|
#: src/rdfeed:82
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Signing in to %s…"
|
||||||
|
msgstr "%s üzerinde oturum açılıyor…"
|
||||||
|
|
||||||
|
#: src/rdfeed:100
|
||||||
|
msgid ""
|
||||||
|
"The password could not be saved in the keyring, so each app asks for it."
|
||||||
|
msgstr ""
|
||||||
|
"Parola anahtarlığa kaydedilemedi, bu yüzden her uygulama parolayı soracak."
|
||||||
|
|
||||||
|
#: src/rdfeed:111
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "1 app from %s is now in your app menu."
|
||||||
|
msgstr "%s kaynağından 1 uygulama artık uygulama menünüzde."
|
||||||
|
|
||||||
|
#: src/rdfeed:113
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d apps from %s are now in your app menu."
|
||||||
|
msgstr "%d uygulama (%s) artık uygulama menünüzde."
|
||||||
|
|
||||||
|
#: src/rdfeed:129
|
||||||
|
msgid ""
|
||||||
|
"Your IT department has the address. A work e-mail address often works too."
|
||||||
|
msgstr "Adres BT bölümünüzde. Çoğu zaman iş e-posta adresiniz de işe yarar."
|
||||||
|
|
||||||
|
#: src/rdfeed:130
|
||||||
|
msgid "Feed address, server or e-mail:"
|
||||||
|
msgstr "Akış adresi, sunucu ya da e-posta:"
|
||||||
|
|
||||||
|
#: src/rdfeed:137
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No feed is registered for %s. Ask your IT department for the address."
|
||||||
|
msgstr "%s için kayıtlı bir akış yok. Adresi BT bölümünüze sorun."
|
||||||
|
|
||||||
|
#: src/rdfeed:142 src/rdfeed:156
|
||||||
|
msgid "User name (DOMAIN\\name or name@domain):"
|
||||||
|
msgstr "Kullanıcı adı (ALAN\\ad ya da ad@alan):"
|
||||||
|
|
||||||
|
#: src/rdfeed:146 src/rdfeed:159
|
||||||
|
msgid "Password:"
|
||||||
|
msgstr "Parola:"
|
||||||
|
|
||||||
|
#: src/rdfeed:178 src/rdfeed:236
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No workspace yet. Add one with `%s add`."
|
||||||
|
msgstr "Henüz çalışma alanı yok. `%s add` ile bir tane ekleyin."
|
||||||
|
|
||||||
|
#: src/rdfeed:187 src/lib/launch.sh:220
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No password saved for %s. Sign in again in rdfeed."
|
||||||
|
msgstr "%s için kayıtlı parola yok. rdfeed içinde yeniden oturum açın."
|
||||||
|
|
||||||
|
#: src/rdfeed:191 src/lib/launch.sh:149
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Password for %s:"
|
||||||
|
msgstr "%s parolası:"
|
||||||
|
|
||||||
|
#: src/rdfeed:195
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Refreshing %s…"
|
||||||
|
msgstr "%s yenileniyor…"
|
||||||
|
|
||||||
|
#: src/rdfeed:205
|
||||||
|
msgid "The password no longer works. Sign in again in rdfeed."
|
||||||
|
msgstr "Parola artık çalışmıyor. rdfeed içinde yeniden oturum açın."
|
||||||
|
|
||||||
|
#: src/rdfeed:214
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s: %d apps"
|
||||||
|
msgstr "%s: %d uygulama"
|
||||||
|
|
||||||
|
#: src/rdfeed:222
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Which workspace? See `%s list` for the names."
|
||||||
|
msgstr "Hangi çalışma alanı? Adları `%s list` gösterir."
|
||||||
|
|
||||||
|
#: src/rdfeed:229
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s and its apps are removed."
|
||||||
|
msgstr "%s ve uygulamaları kaldırıldı."
|
||||||
|
|
||||||
|
#: src/rdfeed:253
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Which app? See `%s list`."
|
||||||
|
msgstr "Hangi uygulama? `%s list` komutuna bakın."
|
||||||
|
|
||||||
|
#: src/rdfeed:268 src/lib/launch.sh:199
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "There is no app %s. See `%s list`."
|
||||||
|
msgstr "%s diye bir uygulama yok. `%s list` komutuna bakın."
|
||||||
|
|
||||||
|
#: src/rdfeed:270
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Several workspaces have %s. Write it as WORKSPACE/APP, see `%s list`."
|
||||||
|
msgstr ""
|
||||||
|
"%s birden çok çalışma alanında var. ÇALIŞMA_ALANI/UYGULAMA biçiminde yazın, "
|
||||||
|
"`%s list` komutuna bakın."
|
||||||
|
|
||||||
|
#: src/rdfeed:278
|
||||||
|
msgid "Usage: rdfeed [command]"
|
||||||
|
msgstr "Kullanım: rdfeed [komut]"
|
||||||
|
|
||||||
|
#: src/rdfeed:280
|
||||||
|
msgid "Commands:"
|
||||||
|
msgstr "Komutlar:"
|
||||||
|
|
||||||
|
#: src/rdfeed:281
|
||||||
|
msgid "Add the workspace of your company"
|
||||||
|
msgstr "Şirketinizin çalışma alanını ekle"
|
||||||
|
|
||||||
|
#: src/rdfeed:282
|
||||||
|
msgid "Load the apps again"
|
||||||
|
msgstr "Uygulamaları yeniden yükle"
|
||||||
|
|
||||||
|
#: src/rdfeed:283
|
||||||
|
msgid "List the workspaces and their apps"
|
||||||
|
msgstr "Çalışma alanlarını ve uygulamalarını listele"
|
||||||
|
|
||||||
|
#: src/rdfeed:284
|
||||||
|
msgid "Start an app, or open a file with it"
|
||||||
|
msgstr "Bir uygulama başlat ya da onunla bir dosya aç"
|
||||||
|
|
||||||
|
#: src/rdfeed:285
|
||||||
|
msgid "Remove a workspace and its apps"
|
||||||
|
msgstr "Bir çalışma alanını ve uygulamalarını kaldır"
|
||||||
|
|
||||||
|
#: src/rdfeed:286
|
||||||
|
msgid "Show this help"
|
||||||
|
msgstr "Bu yardımı göster"
|
||||||
|
|
||||||
|
#: src/rdfeed:287
|
||||||
|
msgid "Show the version"
|
||||||
|
msgstr "Sürümü göster"
|
||||||
|
|
||||||
|
#: src/rdfeed:289
|
||||||
|
msgid "Without a command an interactive menu is shown."
|
||||||
|
msgstr "Komut verilmezse etkileşimli bir menü gösterilir."
|
||||||
|
|
||||||
|
#: src/rdfeed:313
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Unknown command: %s"
|
||||||
|
msgstr "Bilinmeyen komut: %s"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:174 src/lib/menu.sh:251
|
||||||
|
msgid "never"
|
||||||
|
msgstr "hiçbir zaman"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:184
|
||||||
|
msgid "just now"
|
||||||
|
msgstr "az önce"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:186
|
||||||
|
msgid "1 minute ago"
|
||||||
|
msgstr "1 dakika önce"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:189
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d minutes ago"
|
||||||
|
msgstr "%d dakika önce"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:191
|
||||||
|
msgid "1 hour ago"
|
||||||
|
msgstr "1 saat önce"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:194
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d hours ago"
|
||||||
|
msgstr "%d saat önce"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:196
|
||||||
|
msgid "1 day ago"
|
||||||
|
msgstr "1 gün önce"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:199
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d days ago"
|
||||||
|
msgstr "%d gün önce"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:169
|
||||||
|
msgid ""
|
||||||
|
"Your password has expired. Change it on Windows or on the RD Web page, then "
|
||||||
|
"sign in again in rdfeed."
|
||||||
|
msgstr ""
|
||||||
|
"Parolanızın süresi doldu. Windows'ta ya da RD Web sayfasında değiştirin, "
|
||||||
|
"sonra rdfeed içinde yeniden oturum açın."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:171
|
||||||
|
msgid "Your account is locked. Ask your IT department."
|
||||||
|
msgstr "Hesabınız kilitli. BT bölümünüze sorun."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:173
|
||||||
|
msgid "The server did not accept the password. Sign in again in rdfeed."
|
||||||
|
msgstr "Sunucu parolayı kabul etmedi. rdfeed içinde yeniden oturum açın."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:175
|
||||||
|
msgid "Could not reach the server. Check the internet connection."
|
||||||
|
msgstr "Sunucuya ulaşılamadı. İnternet bağlantısını kontrol edin."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:178
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "FreeRDP stopped: %s"
|
||||||
|
msgstr "FreeRDP durdu: %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:205 src/lib/launch.sh:206
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "File not found: %s"
|
||||||
|
msgstr "Dosya bulunamadı: %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:212 src/lib/launch.sh:213
|
||||||
|
msgid ""
|
||||||
|
"FreeRDP 3 is missing. Install it to start the apps (the package is called "
|
||||||
|
"freerdp)."
|
||||||
|
msgstr ""
|
||||||
|
"FreeRDP 3 eksik. Uygulamaları başlatmak için kurun (paketin adı freerdp)."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:277
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Opening %s in %s"
|
||||||
|
msgstr "%s, %s ile açılıyor"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:278
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "If it does not open by itself, open it in the app from the drive %s."
|
||||||
|
msgstr "Kendiliğinden açılmazsa uygulamada %s sürücüsünden açın."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:280
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Starting %s"
|
||||||
|
msgstr "%s başlatılıyor"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:281
|
||||||
|
msgid ""
|
||||||
|
"If your company uses MFA, confirm the sign-in in your authenticator app."
|
||||||
|
msgstr ""
|
||||||
|
"Şirketiniz MFA kullanıyorsa doğrulama uygulamanızda oturum açmayı onaylayın."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:290
|
||||||
|
msgid "A second sign-in is coming"
|
||||||
|
msgstr "İkinci bir oturum açma geliyor"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:291
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid ""
|
||||||
|
"Confirm the second prompt in your authenticator app too. It comes in about "
|
||||||
|
"%d seconds."
|
||||||
|
msgstr ""
|
||||||
|
"İkinci isteği de doğrulama uygulamanızda onaylayın. Yaklaşık %d saniye "
|
||||||
|
"içinde gelir."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:301
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s could not start"
|
||||||
|
msgstr "%s başlatılamadı"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:303
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Log: %s"
|
||||||
|
msgstr "Günlük: %s"
|
||||||
|
|
||||||
|
#. TRANSLATORS: the letter after "[" is the key for yes. y works too.
|
||||||
|
#: src/lib/menu.sh:93
|
||||||
|
msgid "[y/N]"
|
||||||
|
msgstr "[e/H]"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:163
|
||||||
|
msgid "1 app"
|
||||||
|
msgstr "1 uygulama"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:165
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d apps"
|
||||||
|
msgstr "%d uygulama"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:190
|
||||||
|
msgid "No workspace yet. Add the one of your company with [1]."
|
||||||
|
msgstr "Henüz çalışma alanı yok. Şirketinizinkini [1] ile ekleyin."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:233
|
||||||
|
msgid "off"
|
||||||
|
msgstr "kapalı"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:234
|
||||||
|
msgid "home folder"
|
||||||
|
msgstr "ev klasörü"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:237
|
||||||
|
msgid "Documents"
|
||||||
|
msgstr "Belgeler"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:238
|
||||||
|
msgid "Downloads"
|
||||||
|
msgstr "İndirilenler"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:243
|
||||||
|
msgid "not found"
|
||||||
|
msgstr "bulunamadı"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:246
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "automatic (%d %%)"
|
||||||
|
msgstr "otomatik (%%%d)"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:248
|
||||||
|
msgid "classic"
|
||||||
|
msgstr "klasik"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:249
|
||||||
|
msgid "GFX"
|
||||||
|
msgstr "GFX"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:250
|
||||||
|
msgid "always"
|
||||||
|
msgstr "her zaman"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:252
|
||||||
|
msgid "automatic"
|
||||||
|
msgstr "otomatik"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:261
|
||||||
|
msgid ""
|
||||||
|
"The apps only see the files on the server. Pick a folder to open and save "
|
||||||
|
"files of this computer in them."
|
||||||
|
msgstr ""
|
||||||
|
"Uygulamalar yalnızca sunucudaki dosyaları görür. Bu bilgisayarın dosyalarını "
|
||||||
|
"açıp kaydedebilmeleri için bir klasör seçin."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:262
|
||||||
|
msgid ""
|
||||||
|
"The apps can open and save files in this folder. It shows up as a drive in "
|
||||||
|
"them."
|
||||||
|
msgstr ""
|
||||||
|
"Uygulamalar bu klasörde dosya açıp kaydedebilir. Klasör onlarda bir sürücü "
|
||||||
|
"olarak görünür."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:263
|
||||||
|
msgid ""
|
||||||
|
"Offers the apps under \"Open with\" for their file types, for example Word "
|
||||||
|
"for .docx. The folder of the file is shared with the app while it is open."
|
||||||
|
msgstr ""
|
||||||
|
"Uygulamaları dosya türleri için \"Birlikte aç\" menüsünde sunar, "
|
||||||
|
"örneğin .docx için Word. Dosya açık olduğu sürece klasörü paylaşılır."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:264
|
||||||
|
msgid ""
|
||||||
|
"How big the apps are drawn. Automatic follows the scale of your desktop, so "
|
||||||
|
"they are sharp and the right size."
|
||||||
|
msgstr ""
|
||||||
|
"Uygulamaların ne kadar büyük çizileceği. Otomatik, masaüstünüzün ölçeğini "
|
||||||
|
"izler; böylece keskin ve doğru boyutta olurlar."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:265
|
||||||
|
msgid "Plays the sound of the apps here, for calls and videos."
|
||||||
|
msgstr "Uygulamaların sesini burada çalar; aramalar ve videolar için."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:266
|
||||||
|
msgid ""
|
||||||
|
"Paints every window right. But some servers end the session right after "
|
||||||
|
"signing in, then go back to GFX."
|
||||||
|
msgstr "Her pencereyi doğru çizer. Ama bazı sunucular oturum açar açmaz oturumu kapatır, o zaman GFX'e geri dönün."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:267
|
||||||
|
msgid ""
|
||||||
|
"Works with every server. FreeRDP may not repaint a window after it was "
|
||||||
|
"minimized or resized."
|
||||||
|
msgstr "Her sunucuyla çalışır. FreeRDP küçültülen ya da boyutu değişen bir pencereyi yeniden çizmeyebilir."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:268
|
||||||
|
msgid ""
|
||||||
|
"Always waits before the second sign-in. For when the second prompt does not "
|
||||||
|
"come with automatic."
|
||||||
|
msgstr ""
|
||||||
|
"İkinci oturum açmadan önce hep bekler. Otomatikte ikinci istek gelmiyorsa "
|
||||||
|
"kullanın."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:269
|
||||||
|
msgid "Never waits. Right when your company has no MFA."
|
||||||
|
msgstr "Hiç beklemez. Şirketiniz MFA kullanmıyorsa doğru seçim."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:270
|
||||||
|
msgid ""
|
||||||
|
"Many servers with MFA need a second sign-in, and its prompt only comes after "
|
||||||
|
"a short wait. Automatic waits when the first sign-in took a while."
|
||||||
|
msgstr ""
|
||||||
|
"MFA kullanan birçok sunucu ikinci bir oturum açma ister ve isteği ancak kısa "
|
||||||
|
"bir beklemeden sonra gelir. Otomatik, ilk oturum açma biraz sürdüyse bekler."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:273
|
||||||
|
msgid ""
|
||||||
|
"Loads the list of apps again once a day, so new apps show up by themselves."
|
||||||
|
msgstr ""
|
||||||
|
"Uygulama listesini günde bir kez yeniden yükler; böylece yeni uygulamalar "
|
||||||
|
"kendiliğinden görünür."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:275 src/lib/menu.sh:330
|
||||||
|
msgid "Needs a systemd user session."
|
||||||
|
msgstr "Bir systemd kullanıcı oturumu gerekir."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:322 src/lib/menu.sh:325
|
||||||
|
msgid "Could not save the setting."
|
||||||
|
msgstr "Ayar kaydedilemedi."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:371 src/lib/menu.sh:609
|
||||||
|
msgid "Settings"
|
||||||
|
msgstr "Ayarlar"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:372
|
||||||
|
msgid "↑↓ select ←→ or Space: change q: back"
|
||||||
|
msgstr "↑↓ seç ←→ ya da Boşluk: değiştir q: geri"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:373
|
||||||
|
msgid "ON"
|
||||||
|
msgstr "AÇIK"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:374
|
||||||
|
msgid "OFF"
|
||||||
|
msgstr "KAPALI"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:456 src/lib/menu.sh:608
|
||||||
|
msgid "Workspaces"
|
||||||
|
msgstr "Çalışma alanları"
|
||||||
|
|
||||||
|
#. TRANSLATORS: keep the letters, they are the keys.
|
||||||
|
#: src/lib/menu.sh:458
|
||||||
|
msgid ""
|
||||||
|
"↑↓ select r: refresh s: sign in again d: remove a: add q: back"
|
||||||
|
msgstr ""
|
||||||
|
"↑↓ seç r: yenile s: yeniden oturum aç d: kaldır a: ekle q: geri"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:459
|
||||||
|
msgid "No workspace yet. Press a to add one."
|
||||||
|
msgstr "Henüz çalışma alanı yok. Eklemek için a tuşuna basın."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:484
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "refreshed %s"
|
||||||
|
msgstr "%s yenilendi"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:512
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Remove \"%s\" and its apps?"
|
||||||
|
msgstr "\"%s\" ve uygulamaları kaldırılsın mı?"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:536 src/lib/menu.sh:607
|
||||||
|
msgid "Start an app"
|
||||||
|
msgstr "Bir uygulama başlat"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:537
|
||||||
|
msgid "↑↓ select Enter: start q: back"
|
||||||
|
msgstr "↑↓ seç Enter: başlat q: geri"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:538
|
||||||
|
msgid "No apps yet. Add a workspace first."
|
||||||
|
msgstr "Henüz uygulama yok. Önce bir çalışma alanı ekleyin."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:579
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s is starting. It opens in a window of its own."
|
||||||
|
msgstr "%s başlatılıyor. Kendi penceresinde açılır."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:606
|
||||||
|
msgid "Add a workspace"
|
||||||
|
msgstr "Çalışma alanı ekle"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:610
|
||||||
|
msgid "Quit"
|
||||||
|
msgstr "Çık"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:163
|
||||||
|
msgid "The server did not accept the user name or password."
|
||||||
|
msgstr "Sunucu kullanıcı adını ya da parolayı kabul etmedi."
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:164
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not reach the server: %s"
|
||||||
|
msgstr "Sunucuya ulaşılamadı: %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:165
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "There is no RD Web feed at %s."
|
||||||
|
msgstr "%s adresinde RD Web akışı yok."
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:166
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not load the apps: %s"
|
||||||
|
msgstr "Uygulamalar yüklenemedi: %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:282
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Remote desktop of %s"
|
||||||
|
msgstr "%s uzak masaüstü"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:284
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s from %s"
|
||||||
|
msgstr "%s (%s)"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Apps"
|
||||||
|
msgstr "Uygulamalar"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Share a folder"
|
||||||
|
msgstr "Klasör paylaş"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Open files with the apps"
|
||||||
|
msgstr "Dosyaları uygulamalarla aç"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Size"
|
||||||
|
msgstr "Boyut"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Sound"
|
||||||
|
msgstr "Ses"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Graphics"
|
||||||
|
msgstr "Grafik"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Signing in"
|
||||||
|
msgstr "Oturum açma"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Wait for the second MFA prompt"
|
||||||
|
msgstr "İkinci MFA isteğini bekle"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Refresh the apps every day"
|
||||||
|
msgstr "Uygulamaları her gün yenile"
|
||||||
@@ -0,0 +1,559 @@
|
|||||||
|
# Ukrainian translation for rdfeed.
|
||||||
|
# Copyright (C) 2026 Felitendo
|
||||||
|
# This file is distributed under the same license as rdfeed.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: rdfeed 1.0.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: https://github.com/LoonixTools/rdfeed/issues\n"
|
||||||
|
"POT-Creation-Date: 2026-10-02 10:29+0200\n"
|
||||||
|
"PO-Revision-Date: 2026-10-02 09:30+0200\n"
|
||||||
|
"Last-Translator: Felitendo\n"
|
||||||
|
"Language-Team: Ukrainian\n"
|
||||||
|
"Language: uk\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: src/rdfeed:79 src/rdfeed:103
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not write to %s."
|
||||||
|
msgstr "Не вдалося записати в %s."
|
||||||
|
|
||||||
|
#: src/rdfeed:82
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Signing in to %s…"
|
||||||
|
msgstr "Вхід на %s…"
|
||||||
|
|
||||||
|
#: src/rdfeed:100
|
||||||
|
msgid ""
|
||||||
|
"The password could not be saved in the keyring, so each app asks for it."
|
||||||
|
msgstr ""
|
||||||
|
"Не вдалося зберегти пароль у в'язці ключів, тож кожен застосунок питатиме "
|
||||||
|
"його."
|
||||||
|
|
||||||
|
#: src/rdfeed:111
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "1 app from %s is now in your app menu."
|
||||||
|
msgstr "1 застосунок із %s тепер у меню застосунків."
|
||||||
|
|
||||||
|
#: src/rdfeed:113
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d apps from %s are now in your app menu."
|
||||||
|
msgstr "Застосунків: %d (із %s). Тепер вони в меню застосунків."
|
||||||
|
|
||||||
|
#: src/rdfeed:129
|
||||||
|
msgid ""
|
||||||
|
"Your IT department has the address. A work e-mail address often works too."
|
||||||
|
msgstr ""
|
||||||
|
"Адресу має ваш ІТ-відділ. Часто підходить і робоча адреса електронної пошти."
|
||||||
|
|
||||||
|
#: src/rdfeed:130
|
||||||
|
msgid "Feed address, server or e-mail:"
|
||||||
|
msgstr "Адреса фіда, сервер або e-mail:"
|
||||||
|
|
||||||
|
#: src/rdfeed:137
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No feed is registered for %s. Ask your IT department for the address."
|
||||||
|
msgstr "Для %s не зареєстровано фід. Запитайте адресу в ІТ-відділі."
|
||||||
|
|
||||||
|
#: src/rdfeed:142 src/rdfeed:156
|
||||||
|
msgid "User name (DOMAIN\\name or name@domain):"
|
||||||
|
msgstr "Ім'я користувача (ДОМЕН\\ім'я або ім'я@домен):"
|
||||||
|
|
||||||
|
#: src/rdfeed:146 src/rdfeed:159
|
||||||
|
msgid "Password:"
|
||||||
|
msgstr "Пароль:"
|
||||||
|
|
||||||
|
#: src/rdfeed:178 src/rdfeed:236
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No workspace yet. Add one with `%s add`."
|
||||||
|
msgstr "Робочих просторів ще немає. Додайте його командою `%s add`."
|
||||||
|
|
||||||
|
#: src/rdfeed:187 src/lib/launch.sh:220
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No password saved for %s. Sign in again in rdfeed."
|
||||||
|
msgstr "Для %s не збережено пароль. Увійдіть знову в rdfeed."
|
||||||
|
|
||||||
|
#: src/rdfeed:191 src/lib/launch.sh:149
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Password for %s:"
|
||||||
|
msgstr "Пароль для %s:"
|
||||||
|
|
||||||
|
#: src/rdfeed:195
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Refreshing %s…"
|
||||||
|
msgstr "Оновлення %s…"
|
||||||
|
|
||||||
|
#: src/rdfeed:205
|
||||||
|
msgid "The password no longer works. Sign in again in rdfeed."
|
||||||
|
msgstr "Пароль більше не підходить. Увійдіть знову в rdfeed."
|
||||||
|
|
||||||
|
#: src/rdfeed:214
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s: %d apps"
|
||||||
|
msgstr "%s: застосунків: %d"
|
||||||
|
|
||||||
|
#: src/rdfeed:222
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Which workspace? See `%s list` for the names."
|
||||||
|
msgstr "Який робочий простір? Назви показує `%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:229
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s and its apps are removed."
|
||||||
|
msgstr "Видалено: %s разом із застосунками."
|
||||||
|
|
||||||
|
#: src/rdfeed:253
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Which app? See `%s list`."
|
||||||
|
msgstr "Який застосунок? Дивіться `%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:268 src/lib/launch.sh:199
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "There is no app %s. See `%s list`."
|
||||||
|
msgstr "Застосунку %s немає. Дивіться `%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:270
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Several workspaces have %s. Write it as WORKSPACE/APP, see `%s list`."
|
||||||
|
msgstr ""
|
||||||
|
"%s є в кількох робочих просторах. Вкажіть у вигляді ПРОСТІР/ЗАСТОСУНОК, "
|
||||||
|
"дивіться `%s list`."
|
||||||
|
|
||||||
|
#: src/rdfeed:278
|
||||||
|
msgid "Usage: rdfeed [command]"
|
||||||
|
msgstr "Використання: rdfeed [команда]"
|
||||||
|
|
||||||
|
#: src/rdfeed:280
|
||||||
|
msgid "Commands:"
|
||||||
|
msgstr "Команди:"
|
||||||
|
|
||||||
|
#: src/rdfeed:281
|
||||||
|
msgid "Add the workspace of your company"
|
||||||
|
msgstr "Додати робочий простір вашої компанії"
|
||||||
|
|
||||||
|
#: src/rdfeed:282
|
||||||
|
msgid "Load the apps again"
|
||||||
|
msgstr "Завантажити застосунки знову"
|
||||||
|
|
||||||
|
#: src/rdfeed:283
|
||||||
|
msgid "List the workspaces and their apps"
|
||||||
|
msgstr "Показати робочі простори та їхні застосунки"
|
||||||
|
|
||||||
|
#: src/rdfeed:284
|
||||||
|
msgid "Start an app, or open a file with it"
|
||||||
|
msgstr "Запустити застосунок або відкрити в ньому файл"
|
||||||
|
|
||||||
|
#: src/rdfeed:285
|
||||||
|
msgid "Remove a workspace and its apps"
|
||||||
|
msgstr "Видалити робочий простір і його застосунки"
|
||||||
|
|
||||||
|
#: src/rdfeed:286
|
||||||
|
msgid "Show this help"
|
||||||
|
msgstr "Показати цю довідку"
|
||||||
|
|
||||||
|
#: src/rdfeed:287
|
||||||
|
msgid "Show the version"
|
||||||
|
msgstr "Показати версію"
|
||||||
|
|
||||||
|
#: src/rdfeed:289
|
||||||
|
msgid "Without a command an interactive menu is shown."
|
||||||
|
msgstr "Без команди відкривається інтерактивне меню."
|
||||||
|
|
||||||
|
#: src/rdfeed:313
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Unknown command: %s"
|
||||||
|
msgstr "Невідома команда: %s"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:174 src/lib/menu.sh:251
|
||||||
|
msgid "never"
|
||||||
|
msgstr "ніколи"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:184
|
||||||
|
msgid "just now"
|
||||||
|
msgstr "щойно"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:186
|
||||||
|
msgid "1 minute ago"
|
||||||
|
msgstr "1 хвилину тому"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:189
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d minutes ago"
|
||||||
|
msgstr "%d хв тому"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:191
|
||||||
|
msgid "1 hour ago"
|
||||||
|
msgstr "1 годину тому"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:194
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d hours ago"
|
||||||
|
msgstr "%d год тому"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:196
|
||||||
|
msgid "1 day ago"
|
||||||
|
msgstr "1 день тому"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:199
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d days ago"
|
||||||
|
msgstr "%d дн. тому"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:169
|
||||||
|
msgid ""
|
||||||
|
"Your password has expired. Change it on Windows or on the RD Web page, then "
|
||||||
|
"sign in again in rdfeed."
|
||||||
|
msgstr ""
|
||||||
|
"Термін дії пароля минув. Змініть його у Windows або на сторінці RD Web, "
|
||||||
|
"потім знову увійдіть у rdfeed."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:171
|
||||||
|
msgid "Your account is locked. Ask your IT department."
|
||||||
|
msgstr "Обліковий запис заблоковано. Зверніться до ІТ-відділу."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:173
|
||||||
|
msgid "The server did not accept the password. Sign in again in rdfeed."
|
||||||
|
msgstr "Сервер не прийняв пароль. Увійдіть знову в rdfeed."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:175
|
||||||
|
msgid "Could not reach the server. Check the internet connection."
|
||||||
|
msgstr "Не вдалося зв'язатися із сервером. Перевірте підключення до інтернету."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:178
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "FreeRDP stopped: %s"
|
||||||
|
msgstr "FreeRDP зупинився: %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:205 src/lib/launch.sh:206
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "File not found: %s"
|
||||||
|
msgstr "Файл не знайдено: %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:212 src/lib/launch.sh:213
|
||||||
|
msgid ""
|
||||||
|
"FreeRDP 3 is missing. Install it to start the apps (the package is called "
|
||||||
|
"freerdp)."
|
||||||
|
msgstr ""
|
||||||
|
"Немає FreeRDP 3. Установіть його, щоб запускати застосунки (пакет "
|
||||||
|
"називається freerdp)."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:277
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Opening %s in %s"
|
||||||
|
msgstr "Відкриття %s у %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:278
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "If it does not open by itself, open it in the app from the drive %s."
|
||||||
|
msgstr "Якщо файл не відкриється сам, відкрийте його в застосунку з диска %s."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:280
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Starting %s"
|
||||||
|
msgstr "Запуск: %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:281
|
||||||
|
msgid ""
|
||||||
|
"If your company uses MFA, confirm the sign-in in your authenticator app."
|
||||||
|
msgstr ""
|
||||||
|
"Якщо ваша компанія використовує MFA, підтвердьте вхід у застосунку "
|
||||||
|
"автентифікації."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:290
|
||||||
|
msgid "A second sign-in is coming"
|
||||||
|
msgstr "Буде другий вхід"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:291
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid ""
|
||||||
|
"Confirm the second prompt in your authenticator app too. It comes in about "
|
||||||
|
"%d seconds."
|
||||||
|
msgstr ""
|
||||||
|
"Підтвердьте і другий запит у застосунку автентифікації. Він надійде "
|
||||||
|
"приблизно за %d с."
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:301
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s could not start"
|
||||||
|
msgstr "Не вдалося запустити: %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:303
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Log: %s"
|
||||||
|
msgstr "Журнал: %s"
|
||||||
|
|
||||||
|
#. TRANSLATORS: the letter after "[" is the key for yes. y works too.
|
||||||
|
#: src/lib/menu.sh:93
|
||||||
|
msgid "[y/N]"
|
||||||
|
msgstr "[т/Н]"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:163
|
||||||
|
msgid "1 app"
|
||||||
|
msgstr "1 застосунок"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:165
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d apps"
|
||||||
|
msgstr "застосунків: %d"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:190
|
||||||
|
msgid "No workspace yet. Add the one of your company with [1]."
|
||||||
|
msgstr "Робочих просторів ще немає. Додайте простір вашої компанії через [1]."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:233
|
||||||
|
msgid "off"
|
||||||
|
msgstr "вимк."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:234
|
||||||
|
msgid "home folder"
|
||||||
|
msgstr "домашня тека"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:237
|
||||||
|
msgid "Documents"
|
||||||
|
msgstr "Документи"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:238
|
||||||
|
msgid "Downloads"
|
||||||
|
msgstr "Завантаження"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:243
|
||||||
|
msgid "not found"
|
||||||
|
msgstr "не знайдено"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:246
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "automatic (%d %%)"
|
||||||
|
msgstr "автоматично (%d %%)"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:248
|
||||||
|
msgid "classic"
|
||||||
|
msgstr "класичний"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:249
|
||||||
|
msgid "GFX"
|
||||||
|
msgstr "GFX"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:250
|
||||||
|
msgid "always"
|
||||||
|
msgstr "завжди"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:252
|
||||||
|
msgid "automatic"
|
||||||
|
msgstr "автоматично"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:261
|
||||||
|
msgid ""
|
||||||
|
"The apps only see the files on the server. Pick a folder to open and save "
|
||||||
|
"files of this computer in them."
|
||||||
|
msgstr ""
|
||||||
|
"Застосунки бачать лише файли на сервері. Виберіть теку, щоб вони могли "
|
||||||
|
"відкривати й зберігати файли цього комп'ютера."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:262
|
||||||
|
msgid ""
|
||||||
|
"The apps can open and save files in this folder. It shows up as a drive in "
|
||||||
|
"them."
|
||||||
|
msgstr ""
|
||||||
|
"Застосунки можуть відкривати й зберігати файли в цій теці. У них вона видна "
|
||||||
|
"як диск."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:263
|
||||||
|
msgid ""
|
||||||
|
"Offers the apps under \"Open with\" for their file types, for example Word "
|
||||||
|
"for .docx. The folder of the file is shared with the app while it is open."
|
||||||
|
msgstr ""
|
||||||
|
"Пропонує застосунки в «Відкрити за допомогою» для їхніх типів файлів, "
|
||||||
|
"наприклад Word для .docx. Тека файлу відкрита для сервера, доки файл "
|
||||||
|
"відкритий."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:264
|
||||||
|
msgid ""
|
||||||
|
"How big the apps are drawn. Automatic follows the scale of your desktop, so "
|
||||||
|
"they are sharp and the right size."
|
||||||
|
msgstr ""
|
||||||
|
"Наскільки великими малюються застосунки. «Автоматично» стежить за масштабом "
|
||||||
|
"стільниці, тож вони чіткі й потрібного розміру."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:265
|
||||||
|
msgid "Plays the sound of the apps here, for calls and videos."
|
||||||
|
msgstr "Відтворює звук застосунків тут, для дзвінків і відео."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:266
|
||||||
|
msgid ""
|
||||||
|
"Paints every window right. But some servers end the session right after "
|
||||||
|
"signing in, then go back to GFX."
|
||||||
|
msgstr "Правильно малює кожне вікно. Але деякі сервери обривають сеанс одразу після входу, тоді поверніться до GFX."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:267
|
||||||
|
msgid ""
|
||||||
|
"Works with every server. FreeRDP may not repaint a window after it was "
|
||||||
|
"minimized or resized."
|
||||||
|
msgstr "Працює з будь-яким сервером. FreeRDP може не перемалювати вікно після згортання чи зміни розміру."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:268
|
||||||
|
msgid ""
|
||||||
|
"Always waits before the second sign-in. For when the second prompt does not "
|
||||||
|
"come with automatic."
|
||||||
|
msgstr ""
|
||||||
|
"Завжди чекає перед другим входом. Якщо з «автоматично» другий запит не "
|
||||||
|
"надходить."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:269
|
||||||
|
msgid "Never waits. Right when your company has no MFA."
|
||||||
|
msgstr "Ніколи не чекає. Підходить, якщо ваша компанія не використовує MFA."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:270
|
||||||
|
msgid ""
|
||||||
|
"Many servers with MFA need a second sign-in, and its prompt only comes after "
|
||||||
|
"a short wait. Automatic waits when the first sign-in took a while."
|
||||||
|
msgstr ""
|
||||||
|
"Багатьом серверам з MFA потрібен другий вхід, і його запит надходить лише "
|
||||||
|
"після короткої паузи. «Автоматично» чекає, якщо перший вхід тривав якийсь "
|
||||||
|
"час."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:273
|
||||||
|
msgid ""
|
||||||
|
"Loads the list of apps again once a day, so new apps show up by themselves."
|
||||||
|
msgstr ""
|
||||||
|
"Раз на день заново завантажує список застосунків, тож нові з'являються самі."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:275 src/lib/menu.sh:330
|
||||||
|
msgid "Needs a systemd user session."
|
||||||
|
msgstr "Потрібен користувацький сеанс systemd."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:322 src/lib/menu.sh:325
|
||||||
|
msgid "Could not save the setting."
|
||||||
|
msgstr "Не вдалося зберегти налаштування."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:371 src/lib/menu.sh:609
|
||||||
|
msgid "Settings"
|
||||||
|
msgstr "Налаштування"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:372
|
||||||
|
msgid "↑↓ select ←→ or Space: change q: back"
|
||||||
|
msgstr "↑↓ вибір ←→ або Пробіл: змінити q: назад"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:373
|
||||||
|
msgid "ON"
|
||||||
|
msgstr "УВІМК"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:374
|
||||||
|
msgid "OFF"
|
||||||
|
msgstr "ВИМК"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:456 src/lib/menu.sh:608
|
||||||
|
msgid "Workspaces"
|
||||||
|
msgstr "Робочі простори"
|
||||||
|
|
||||||
|
#. TRANSLATORS: keep the letters, they are the keys.
|
||||||
|
#: src/lib/menu.sh:458
|
||||||
|
msgid ""
|
||||||
|
"↑↓ select r: refresh s: sign in again d: remove a: add q: back"
|
||||||
|
msgstr ""
|
||||||
|
"↑↓ вибір r: оновити s: увійти знову d: видалити a: додати q: назад"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:459
|
||||||
|
msgid "No workspace yet. Press a to add one."
|
||||||
|
msgstr "Робочих просторів ще немає. Натисніть a, щоб додати."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:484
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "refreshed %s"
|
||||||
|
msgstr "оновлено %s"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:512
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Remove \"%s\" and its apps?"
|
||||||
|
msgstr "Видалити «%s» разом із застосунками?"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:536 src/lib/menu.sh:607
|
||||||
|
msgid "Start an app"
|
||||||
|
msgstr "Запустити застосунок"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:537
|
||||||
|
msgid "↑↓ select Enter: start q: back"
|
||||||
|
msgstr "↑↓ вибір Enter: запустити q: назад"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:538
|
||||||
|
msgid "No apps yet. Add a workspace first."
|
||||||
|
msgstr "Застосунків ще немає. Спершу додайте робочий простір."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:579
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s is starting. It opens in a window of its own."
|
||||||
|
msgstr "Запуск: %s. Відкриється в окремому вікні."
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:606
|
||||||
|
msgid "Add a workspace"
|
||||||
|
msgstr "Додати робочий простір"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:610
|
||||||
|
msgid "Quit"
|
||||||
|
msgstr "Вийти"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:163
|
||||||
|
msgid "The server did not accept the user name or password."
|
||||||
|
msgstr "Сервер не прийняв ім'я користувача або пароль."
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:164
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not reach the server: %s"
|
||||||
|
msgstr "Не вдалося зв'язатися із сервером: %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:165
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "There is no RD Web feed at %s."
|
||||||
|
msgstr "За адресою %s немає фіда RD Web."
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:166
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not load the apps: %s"
|
||||||
|
msgstr "Не вдалося завантажити застосунки: %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:282
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Remote desktop of %s"
|
||||||
|
msgstr "Віддалена стільниця %s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:284
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s from %s"
|
||||||
|
msgstr "%s із %s"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Apps"
|
||||||
|
msgstr "Застосунки"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Share a folder"
|
||||||
|
msgstr "Спільна тека"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Open files with the apps"
|
||||||
|
msgstr "Відкривати файли в застосунках"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Size"
|
||||||
|
msgstr "Розмір"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Sound"
|
||||||
|
msgstr "Звук"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Graphics"
|
||||||
|
msgstr "Графіка"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Signing in"
|
||||||
|
msgstr "Вхід"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Wait for the second MFA prompt"
|
||||||
|
msgstr "Чекати на другий запит MFA"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Refresh the apps every day"
|
||||||
|
msgstr "Оновлювати застосунки щодня"
|
||||||
Executable
+34
@@ -0,0 +1,34 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Collects every translatable string into po/rdfeed.pot and brings the
|
||||||
|
# translations up to date with it.
|
||||||
|
#
|
||||||
|
# The settings labels and headings live in an array in menu.sh and reach
|
||||||
|
# gettext at run time, so xgettext cannot see them; they are pulled out here
|
||||||
|
# first.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.."
|
||||||
|
tmp="$(mktemp -d)"
|
||||||
|
trap 'rm -rf -- "$tmp"' EXIT
|
||||||
|
|
||||||
|
version="$(make -s version)"
|
||||||
|
|
||||||
|
# The labels, as calls xgettext understands, pointing back at menu.sh.
|
||||||
|
awk -F'|' '/^\t"(user|entries|timer)\|/ { sub(/"$/, "", $5); print "rf_msg \"" $5 "\"" }
|
||||||
|
/^\t"group\|/ { sub(/"$/, "", $2); print "rf_msg \"" $2 "\"" }' src/lib/menu.sh > "$tmp/settings.sh"
|
||||||
|
|
||||||
|
xgettext --from-code=UTF-8 --add-comments=TRANSLATORS --package-name=rdfeed --package-version="$version" \
|
||||||
|
--msgid-bugs-address=https://github.com/LoonixTools/rdfeed/issues \
|
||||||
|
-L Shell -k --keyword=rf_msg --keyword=rf_msg_into:2 --keyword=rf_msg_in:2 \
|
||||||
|
-o po/rdfeed.pot src/rdfeed src/lib/*.sh "$tmp/settings.sh"
|
||||||
|
sed -i "s|$tmp/settings.sh:[0-9]*|src/lib/menu.sh|g" po/rdfeed.pot
|
||||||
|
sed -i -e '1i # Translation template for rdfeed.\n# Copyright (C) 2026 Felitendo\n# This file is distributed under the same license as rdfeed.' -e '1,4d' \
|
||||||
|
po/rdfeed.pot
|
||||||
|
|
||||||
|
for po in po/*.po; do
|
||||||
|
[[ -e $po ]] || continue
|
||||||
|
msgmerge --quiet --update --backup=none --no-fuzzy-matching "$po" po/rdfeed.pot
|
||||||
|
done
|
||||||
|
echo "po/rdfeed.pot: $(grep -c '^msgid' po/rdfeed.pot) strings"
|
||||||
+537
@@ -0,0 +1,537 @@
|
|||||||
|
# Simplified Chinese translation for rdfeed.
|
||||||
|
# Copyright (C) 2026 Felitendo
|
||||||
|
# This file is distributed under the same license as rdfeed.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: rdfeed 1.0.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: https://github.com/LoonixTools/rdfeed/issues\n"
|
||||||
|
"POT-Creation-Date: 2026-10-02 10:29+0200\n"
|
||||||
|
"PO-Revision-Date: 2026-10-02 09:30+0200\n"
|
||||||
|
"Last-Translator: Felitendo\n"
|
||||||
|
"Language-Team: Simplified Chinese\n"
|
||||||
|
"Language: zh_CN\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: src/rdfeed:79 src/rdfeed:103
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not write to %s."
|
||||||
|
msgstr "无法写入 %s。"
|
||||||
|
|
||||||
|
#: src/rdfeed:82
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Signing in to %s…"
|
||||||
|
msgstr "正在登录 %s…"
|
||||||
|
|
||||||
|
#: src/rdfeed:100
|
||||||
|
msgid ""
|
||||||
|
"The password could not be saved in the keyring, so each app asks for it."
|
||||||
|
msgstr "无法把密码保存到密钥环,因此每个应用都会询问密码。"
|
||||||
|
|
||||||
|
#: src/rdfeed:111
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "1 app from %s is now in your app menu."
|
||||||
|
msgstr "来自 %s 的 1 个应用已加入应用菜单。"
|
||||||
|
|
||||||
|
#: src/rdfeed:113
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d apps from %s are now in your app menu."
|
||||||
|
msgstr "%d 个应用(来自 %s)已加入应用菜单。"
|
||||||
|
|
||||||
|
#: src/rdfeed:129
|
||||||
|
msgid ""
|
||||||
|
"Your IT department has the address. A work e-mail address often works too."
|
||||||
|
msgstr "地址可以向你的 IT 部门索取。很多时候工作邮箱地址也可以。"
|
||||||
|
|
||||||
|
#: src/rdfeed:130
|
||||||
|
msgid "Feed address, server or e-mail:"
|
||||||
|
msgstr "订阅源地址、服务器或邮箱:"
|
||||||
|
|
||||||
|
#: src/rdfeed:137
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No feed is registered for %s. Ask your IT department for the address."
|
||||||
|
msgstr "%s 没有登记订阅源。请向 IT 部门询问地址。"
|
||||||
|
|
||||||
|
#: src/rdfeed:142 src/rdfeed:156
|
||||||
|
msgid "User name (DOMAIN\\name or name@domain):"
|
||||||
|
msgstr "用户名(域\\用户名 或 用户名@域):"
|
||||||
|
|
||||||
|
#: src/rdfeed:146 src/rdfeed:159
|
||||||
|
msgid "Password:"
|
||||||
|
msgstr "密码:"
|
||||||
|
|
||||||
|
#: src/rdfeed:178 src/rdfeed:236
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No workspace yet. Add one with `%s add`."
|
||||||
|
msgstr "还没有工作区。用 `%s add` 添加一个。"
|
||||||
|
|
||||||
|
#: src/rdfeed:187 src/lib/launch.sh:220
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "No password saved for %s. Sign in again in rdfeed."
|
||||||
|
msgstr "没有保存 %s 的密码。请在 rdfeed 中重新登录。"
|
||||||
|
|
||||||
|
#: src/rdfeed:191 src/lib/launch.sh:149
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Password for %s:"
|
||||||
|
msgstr "%s 的密码:"
|
||||||
|
|
||||||
|
#: src/rdfeed:195
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Refreshing %s…"
|
||||||
|
msgstr "正在刷新 %s…"
|
||||||
|
|
||||||
|
#: src/rdfeed:205
|
||||||
|
msgid "The password no longer works. Sign in again in rdfeed."
|
||||||
|
msgstr "密码已失效。请在 rdfeed 中重新登录。"
|
||||||
|
|
||||||
|
#: src/rdfeed:214
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s: %d apps"
|
||||||
|
msgstr "%s:%d 个应用"
|
||||||
|
|
||||||
|
#: src/rdfeed:222
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Which workspace? See `%s list` for the names."
|
||||||
|
msgstr "哪个工作区?名称见 `%s list`。"
|
||||||
|
|
||||||
|
#: src/rdfeed:229
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s and its apps are removed."
|
||||||
|
msgstr "已移除 %s 及其应用。"
|
||||||
|
|
||||||
|
#: src/rdfeed:253
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Which app? See `%s list`."
|
||||||
|
msgstr "哪个应用?见 `%s list`。"
|
||||||
|
|
||||||
|
#: src/rdfeed:268 src/lib/launch.sh:199
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "There is no app %s. See `%s list`."
|
||||||
|
msgstr "没有应用 %s。见 `%s list`。"
|
||||||
|
|
||||||
|
#: src/rdfeed:270
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Several workspaces have %s. Write it as WORKSPACE/APP, see `%s list`."
|
||||||
|
msgstr "多个工作区都有 %s。请写成 工作区/应用,见 `%s list`。"
|
||||||
|
|
||||||
|
#: src/rdfeed:278
|
||||||
|
msgid "Usage: rdfeed [command]"
|
||||||
|
msgstr "用法:rdfeed [命令]"
|
||||||
|
|
||||||
|
#: src/rdfeed:280
|
||||||
|
msgid "Commands:"
|
||||||
|
msgstr "命令:"
|
||||||
|
|
||||||
|
#: src/rdfeed:281
|
||||||
|
msgid "Add the workspace of your company"
|
||||||
|
msgstr "添加你公司的工作区"
|
||||||
|
|
||||||
|
#: src/rdfeed:282
|
||||||
|
msgid "Load the apps again"
|
||||||
|
msgstr "重新加载应用"
|
||||||
|
|
||||||
|
#: src/rdfeed:283
|
||||||
|
msgid "List the workspaces and their apps"
|
||||||
|
msgstr "列出工作区及其应用"
|
||||||
|
|
||||||
|
#: src/rdfeed:284
|
||||||
|
msgid "Start an app, or open a file with it"
|
||||||
|
msgstr "启动应用,或用它打开文件"
|
||||||
|
|
||||||
|
#: src/rdfeed:285
|
||||||
|
msgid "Remove a workspace and its apps"
|
||||||
|
msgstr "移除工作区及其应用"
|
||||||
|
|
||||||
|
#: src/rdfeed:286
|
||||||
|
msgid "Show this help"
|
||||||
|
msgstr "显示此帮助"
|
||||||
|
|
||||||
|
#: src/rdfeed:287
|
||||||
|
msgid "Show the version"
|
||||||
|
msgstr "显示版本"
|
||||||
|
|
||||||
|
#: src/rdfeed:289
|
||||||
|
msgid "Without a command an interactive menu is shown."
|
||||||
|
msgstr "不带命令时显示交互式菜单。"
|
||||||
|
|
||||||
|
#: src/rdfeed:313
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Unknown command: %s"
|
||||||
|
msgstr "未知命令:%s"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:174 src/lib/menu.sh:251
|
||||||
|
msgid "never"
|
||||||
|
msgstr "从未"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:184
|
||||||
|
msgid "just now"
|
||||||
|
msgstr "刚刚"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:186
|
||||||
|
msgid "1 minute ago"
|
||||||
|
msgstr "1 分钟前"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:189
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d minutes ago"
|
||||||
|
msgstr "%d 分钟前"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:191
|
||||||
|
msgid "1 hour ago"
|
||||||
|
msgstr "1 小时前"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:194
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d hours ago"
|
||||||
|
msgstr "%d 小时前"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:196
|
||||||
|
msgid "1 day ago"
|
||||||
|
msgstr "1 天前"
|
||||||
|
|
||||||
|
#: src/lib/common.sh:199
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d days ago"
|
||||||
|
msgstr "%d 天前"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:169
|
||||||
|
msgid ""
|
||||||
|
"Your password has expired. Change it on Windows or on the RD Web page, then "
|
||||||
|
"sign in again in rdfeed."
|
||||||
|
msgstr ""
|
||||||
|
"你的密码已过期。请在 Windows 或 RD Web 页面上修改,然后在 rdfeed 中重新登录。"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:171
|
||||||
|
msgid "Your account is locked. Ask your IT department."
|
||||||
|
msgstr "你的账户已被锁定。请联系 IT 部门。"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:173
|
||||||
|
msgid "The server did not accept the password. Sign in again in rdfeed."
|
||||||
|
msgstr "服务器没有接受密码。请在 rdfeed 中重新登录。"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:175
|
||||||
|
msgid "Could not reach the server. Check the internet connection."
|
||||||
|
msgstr "无法连接服务器。请检查网络连接。"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:178
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "FreeRDP stopped: %s"
|
||||||
|
msgstr "FreeRDP 已停止:%s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:205 src/lib/launch.sh:206
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "File not found: %s"
|
||||||
|
msgstr "找不到文件:%s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:212 src/lib/launch.sh:213
|
||||||
|
msgid ""
|
||||||
|
"FreeRDP 3 is missing. Install it to start the apps (the package is called "
|
||||||
|
"freerdp)."
|
||||||
|
msgstr "缺少 FreeRDP 3。请安装它以启动应用(软件包名为 freerdp)。"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:277
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Opening %s in %s"
|
||||||
|
msgstr "正在打开 %s(使用 %s)"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:278
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "If it does not open by itself, open it in the app from the drive %s."
|
||||||
|
msgstr "如果没有自动打开,请在应用中从驱动器 %s 打开它。"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:280
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Starting %s"
|
||||||
|
msgstr "正在启动 %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:281
|
||||||
|
msgid ""
|
||||||
|
"If your company uses MFA, confirm the sign-in in your authenticator app."
|
||||||
|
msgstr "如果你的公司使用 MFA,请在身份验证器应用中确认登录。"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:290
|
||||||
|
msgid "A second sign-in is coming"
|
||||||
|
msgstr "即将进行第二次登录"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:291
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid ""
|
||||||
|
"Confirm the second prompt in your authenticator app too. It comes in about "
|
||||||
|
"%d seconds."
|
||||||
|
msgstr "也请在身份验证器应用中确认第二次请求。它大约会在 %d 秒后到达。"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:301
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s could not start"
|
||||||
|
msgstr "无法启动 %s"
|
||||||
|
|
||||||
|
#: src/lib/launch.sh:303
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Log: %s"
|
||||||
|
msgstr "日志:%s"
|
||||||
|
|
||||||
|
#. TRANSLATORS: the letter after "[" is the key for yes. y works too.
|
||||||
|
#: src/lib/menu.sh:93
|
||||||
|
msgid "[y/N]"
|
||||||
|
msgstr "[y/N]"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:163
|
||||||
|
msgid "1 app"
|
||||||
|
msgstr "1 个应用"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:165
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%d apps"
|
||||||
|
msgstr "%d 个应用"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:190
|
||||||
|
msgid "No workspace yet. Add the one of your company with [1]."
|
||||||
|
msgstr "还没有工作区。按 [1] 添加你公司的工作区。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:233
|
||||||
|
msgid "off"
|
||||||
|
msgstr "关"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:234
|
||||||
|
msgid "home folder"
|
||||||
|
msgstr "主文件夹"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:237
|
||||||
|
msgid "Documents"
|
||||||
|
msgstr "文档"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:238
|
||||||
|
msgid "Downloads"
|
||||||
|
msgstr "下载"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:243
|
||||||
|
msgid "not found"
|
||||||
|
msgstr "未找到"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:246
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "automatic (%d %%)"
|
||||||
|
msgstr "自动(%d%%)"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:248
|
||||||
|
msgid "classic"
|
||||||
|
msgstr "经典"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:249
|
||||||
|
msgid "GFX"
|
||||||
|
msgstr "GFX"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:250
|
||||||
|
msgid "always"
|
||||||
|
msgstr "总是"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:252
|
||||||
|
msgid "automatic"
|
||||||
|
msgstr "自动"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:261
|
||||||
|
msgid ""
|
||||||
|
"The apps only see the files on the server. Pick a folder to open and save "
|
||||||
|
"files of this computer in them."
|
||||||
|
msgstr ""
|
||||||
|
"应用只能看到服务器上的文件。选一个文件夹,它们就能打开和保存这台电脑上的文"
|
||||||
|
"件。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:262
|
||||||
|
msgid ""
|
||||||
|
"The apps can open and save files in this folder. It shows up as a drive in "
|
||||||
|
"them."
|
||||||
|
msgstr "应用可以在这个文件夹里打开和保存文件。它在应用中显示为一个驱动器。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:263
|
||||||
|
msgid ""
|
||||||
|
"Offers the apps under \"Open with\" for their file types, for example Word "
|
||||||
|
"for .docx. The folder of the file is shared with the app while it is open."
|
||||||
|
msgstr ""
|
||||||
|
"在“打开方式”中按文件类型提供这些应用,例如用 Word 打开 .docx。文件打开期间,"
|
||||||
|
"它所在的文件夹会被共享。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:264
|
||||||
|
msgid ""
|
||||||
|
"How big the apps are drawn. Automatic follows the scale of your desktop, so "
|
||||||
|
"they are sharp and the right size."
|
||||||
|
msgstr "应用绘制的大小。“自动”会跟随桌面的缩放,这样应用清晰且大小合适。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:265
|
||||||
|
msgid "Plays the sound of the apps here, for calls and videos."
|
||||||
|
msgstr "在这台电脑上播放应用的声音,用于通话和视频。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:266
|
||||||
|
msgid ""
|
||||||
|
"Paints every window right. But some servers end the session right after "
|
||||||
|
"signing in, then go back to GFX."
|
||||||
|
msgstr "能正确绘制每个窗口。但有些服务器会在登录后立即结束会话,这时请改回 GFX。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:267
|
||||||
|
msgid ""
|
||||||
|
"Works with every server. FreeRDP may not repaint a window after it was "
|
||||||
|
"minimized or resized."
|
||||||
|
msgstr "适用于所有服务器。窗口最小化或调整大小后,FreeRDP 可能不会重绘。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:268
|
||||||
|
msgid ""
|
||||||
|
"Always waits before the second sign-in. For when the second prompt does not "
|
||||||
|
"come with automatic."
|
||||||
|
msgstr "第二次登录前总是等待。适用于选“自动”时第二次请求没有出现的情况。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:269
|
||||||
|
msgid "Never waits. Right when your company has no MFA."
|
||||||
|
msgstr "从不等待。适合公司不使用 MFA 的情况。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:270
|
||||||
|
msgid ""
|
||||||
|
"Many servers with MFA need a second sign-in, and its prompt only comes after "
|
||||||
|
"a short wait. Automatic waits when the first sign-in took a while."
|
||||||
|
msgstr ""
|
||||||
|
"许多使用 MFA 的服务器需要第二次登录,而它的请求要稍等一下才会出现。“自动”会在"
|
||||||
|
"第一次登录耗时较长时等待。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:273
|
||||||
|
msgid ""
|
||||||
|
"Loads the list of apps again once a day, so new apps show up by themselves."
|
||||||
|
msgstr "每天重新加载一次应用列表,新应用会自动出现。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:275 src/lib/menu.sh:330
|
||||||
|
msgid "Needs a systemd user session."
|
||||||
|
msgstr "需要 systemd 用户会话。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:322 src/lib/menu.sh:325
|
||||||
|
msgid "Could not save the setting."
|
||||||
|
msgstr "无法保存设置。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:371 src/lib/menu.sh:609
|
||||||
|
msgid "Settings"
|
||||||
|
msgstr "设置"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:372
|
||||||
|
msgid "↑↓ select ←→ or Space: change q: back"
|
||||||
|
msgstr "↑↓ 选择 ←→ 或空格:更改 q:返回"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:373
|
||||||
|
msgid "ON"
|
||||||
|
msgstr "开启"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:374
|
||||||
|
msgid "OFF"
|
||||||
|
msgstr "关闭"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:456 src/lib/menu.sh:608
|
||||||
|
msgid "Workspaces"
|
||||||
|
msgstr "工作区"
|
||||||
|
|
||||||
|
#. TRANSLATORS: keep the letters, they are the keys.
|
||||||
|
#: src/lib/menu.sh:458
|
||||||
|
msgid ""
|
||||||
|
"↑↓ select r: refresh s: sign in again d: remove a: add q: back"
|
||||||
|
msgstr "↑↓ 选择 r:刷新 s:重新登录 d:移除 a:添加 q:返回"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:459
|
||||||
|
msgid "No workspace yet. Press a to add one."
|
||||||
|
msgstr "还没有工作区。按 a 添加一个。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:484
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "refreshed %s"
|
||||||
|
msgstr "%s刷新"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:512
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Remove \"%s\" and its apps?"
|
||||||
|
msgstr "移除“%s”及其应用?"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:536 src/lib/menu.sh:607
|
||||||
|
msgid "Start an app"
|
||||||
|
msgstr "启动应用"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:537
|
||||||
|
msgid "↑↓ select Enter: start q: back"
|
||||||
|
msgstr "↑↓ 选择 Enter:启动 q:返回"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:538
|
||||||
|
msgid "No apps yet. Add a workspace first."
|
||||||
|
msgstr "还没有应用。请先添加工作区。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:579
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s is starting. It opens in a window of its own."
|
||||||
|
msgstr "正在启动 %s。它会在单独的窗口中打开。"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:606
|
||||||
|
msgid "Add a workspace"
|
||||||
|
msgstr "添加工作区"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh:610
|
||||||
|
msgid "Quit"
|
||||||
|
msgstr "退出"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:163
|
||||||
|
msgid "The server did not accept the user name or password."
|
||||||
|
msgstr "服务器没有接受用户名或密码。"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:164
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not reach the server: %s"
|
||||||
|
msgstr "无法连接服务器:%s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:165
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "There is no RD Web feed at %s."
|
||||||
|
msgstr "%s 没有 RD Web 订阅源。"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:166
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Could not load the apps: %s"
|
||||||
|
msgstr "无法加载应用:%s"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:282
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "Remote desktop of %s"
|
||||||
|
msgstr "%s 的远程桌面"
|
||||||
|
|
||||||
|
#: src/lib/workspace.sh:284
|
||||||
|
#, sh-printf-format
|
||||||
|
msgid "%s from %s"
|
||||||
|
msgstr "%s(来自 %s)"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Apps"
|
||||||
|
msgstr "应用"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Share a folder"
|
||||||
|
msgstr "共享文件夹"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Open files with the apps"
|
||||||
|
msgstr "用这些应用打开文件"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Size"
|
||||||
|
msgstr "大小"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Sound"
|
||||||
|
msgstr "声音"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Graphics"
|
||||||
|
msgstr "图形"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Signing in"
|
||||||
|
msgstr "登录"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Wait for the second MFA prompt"
|
||||||
|
msgstr "等待第二次 MFA 请求"
|
||||||
|
|
||||||
|
#: src/lib/menu.sh
|
||||||
|
msgid "Refresh the apps every day"
|
||||||
|
msgstr "每天刷新应用"
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
|
||||||
|
<defs>
|
||||||
|
<!-- the tile, in the style of face-unlock -->
|
||||||
|
<linearGradient id="wall" x1="0.1" y1="0" x2="0.9" y2="1">
|
||||||
|
<stop offset="0" stop-color="#0c2f7a"/>
|
||||||
|
<stop offset="0.55" stop-color="#1663d6"/>
|
||||||
|
<stop offset="1" stop-color="#1fb8d6"/>
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient id="glow" cx="0.85" cy="0.1" r="0.6">
|
||||||
|
<stop offset="0" stop-color="#6fa8ff" stop-opacity="0.7"/>
|
||||||
|
<stop offset="1" stop-color="#6fa8ff" stop-opacity="0"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="teal" cx="0.1" cy="0.95" r="0.6">
|
||||||
|
<stop offset="0" stop-color="#3ee8d0" stop-opacity="0.55"/>
|
||||||
|
<stop offset="1" stop-color="#3ee8d0" stop-opacity="0"/>
|
||||||
|
</radialGradient>
|
||||||
|
<linearGradient id="sheen" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="0" stop-color="#ffffff" stop-opacity="0.16"/>
|
||||||
|
<stop offset="0.5" stop-color="#ffffff" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<!-- the window -->
|
||||||
|
<linearGradient id="window" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="0" stop-color="#15161a"/>
|
||||||
|
<stop offset="1" stop-color="#000000"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="glyph" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="0" stop-color="#ffffff"/>
|
||||||
|
<stop offset="1" stop-color="#dbe6f5"/>
|
||||||
|
</linearGradient>
|
||||||
|
<clipPath id="tile"><rect x="24" y="24" width="464" height="464" rx="112"/></clipPath>
|
||||||
|
<filter id="tileShadow" x="-10%" y="-10%" width="120%" height="125%">
|
||||||
|
<feDropShadow dx="0" dy="8" stdDeviation="10" flood-color="#06163a" flood-opacity="0.35"/>
|
||||||
|
</filter>
|
||||||
|
<filter id="windowShadow" x="-25%" y="-25%" width="150%" height="160%">
|
||||||
|
<feDropShadow dx="0" dy="14" stdDeviation="18" flood-color="#000000" flood-opacity="0.45"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- the tile -->
|
||||||
|
<g filter="url(#tileShadow)">
|
||||||
|
<rect x="24" y="24" width="464" height="464" rx="112" fill="url(#wall)"/>
|
||||||
|
</g>
|
||||||
|
<g clip-path="url(#tile)">
|
||||||
|
<rect x="24" y="24" width="464" height="464" fill="url(#glow)"/>
|
||||||
|
<rect x="24" y="24" width="464" height="464" fill="url(#teal)"/>
|
||||||
|
<rect x="24" y="24" width="464" height="464" fill="url(#sheen)"/>
|
||||||
|
</g>
|
||||||
|
<rect x="25" y="25" width="462" height="462" rx="111" fill="none" stroke="#ffffff" stroke-opacity="0.14" stroke-width="2"/>
|
||||||
|
|
||||||
|
<!-- a window, with its title bar -->
|
||||||
|
<rect x="96" y="110" width="320" height="292" rx="56" fill="url(#window)" filter="url(#windowShadow)"/>
|
||||||
|
<rect x="97" y="111" width="318" height="290" rx="55" fill="none" stroke="#ffffff" stroke-opacity="0.07" stroke-width="2"/>
|
||||||
|
<circle cx="146" cy="156" r="9" fill="#ffffff" fill-opacity="0.32"/>
|
||||||
|
<circle cx="176" cy="156" r="9" fill="#ffffff" fill-opacity="0.32"/>
|
||||||
|
<circle cx="206" cy="156" r="9" fill="#ffffff" fill-opacity="0.32"/>
|
||||||
|
|
||||||
|
<!-- the apps in it, and the feed they come from -->
|
||||||
|
<g fill="url(#glyph)">
|
||||||
|
<rect x="152" y="196" width="80" height="80" rx="20"/>
|
||||||
|
<rect x="152" y="294" width="80" height="80" rx="20" fill-opacity="0.9"/>
|
||||||
|
<rect x="250" y="294" width="80" height="80" rx="20" fill-opacity="0.8"/>
|
||||||
|
</g>
|
||||||
|
<g fill="none" stroke="url(#glyph)" stroke-linecap="round" stroke-width="13">
|
||||||
|
<path d="M262 204 A 64 64 0 0 1 326 268"/>
|
||||||
|
<path d="M262 233 A 35 35 0 0 1 297 268"/>
|
||||||
|
</g>
|
||||||
|
<circle cx="268" cy="262" r="10" fill="url(#glyph)"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 100 KiB |
@@ -0,0 +1,8 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Load the apps of the rdfeed workspaces again
|
||||||
|
Documentation=man:rdfeed(1)
|
||||||
|
After=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=@BINDIR@/rdfeed refresh --quiet
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Load the apps of the rdfeed workspaces again every day
|
||||||
|
Documentation=man:rdfeed(1)
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnCalendar=daily
|
||||||
|
RandomizedDelaySec=1h
|
||||||
|
Persistent=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
Executable
+617
@@ -0,0 +1,617 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
# rdfeed-fetch: sign in to an RD Web Access feed and download its apps
|
||||||
|
#
|
||||||
|
# The feed is the one Windows subscribes to under "RemoteApp and Desktop
|
||||||
|
# Connections". Signing in takes NTLM, which curl no longer always has, so it
|
||||||
|
# is done here with nothing but the Python standard library.
|
||||||
|
#
|
||||||
|
# rdfeed-fetch --url URL --user USER [--domain DOMAIN] --out DIR
|
||||||
|
#
|
||||||
|
# The password comes on stdin. DIR gets feed.xml, apps.tsv (app, type, title,
|
||||||
|
# file types), rdp/<app>.rdp and icons/<app>.png. Progress goes to stdout, one
|
||||||
|
# tab-separated line each:
|
||||||
|
#
|
||||||
|
# workspace <name> the name the company gave the feed
|
||||||
|
# domain <domain> the Windows domain the server signed us in to
|
||||||
|
# app <app> <type> <title>
|
||||||
|
# skip <title> <reason>
|
||||||
|
#
|
||||||
|
# Exit codes: 0 done, 10 user name or password wrong, 11 server not reached,
|
||||||
|
# 12 no feed at that address, 2 bad arguments.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2026 Felitendo
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import base64
|
||||||
|
import hmac
|
||||||
|
import html.parser
|
||||||
|
import http.client
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import socket
|
||||||
|
import ssl
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import unicodedata
|
||||||
|
import urllib.parse
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
import zlib
|
||||||
|
|
||||||
|
EXIT_AUTH = 10
|
||||||
|
EXIT_NETWORK = 11
|
||||||
|
EXIT_NOFEED = 12
|
||||||
|
|
||||||
|
# WebFeedLogin.aspx hands back a forms ticket in its body, and the Windows
|
||||||
|
# client returns it as a cookie. The cookie name is not documented and differs
|
||||||
|
# between versions, so it goes out under every name in use. IIS reads its own.
|
||||||
|
FEED_AUTH_COOKIES = ("TSWAFeedAuthCookie", ".ASPXAUTH", "TSWAFeedAuth")
|
||||||
|
|
||||||
|
PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n"
|
||||||
|
|
||||||
|
# The file types of the usual apps, for feeds that list none. Windows only
|
||||||
|
# offers "Open with" for the types the administrator entered, and most never
|
||||||
|
# enter any. Matched as whole words against the program, the title and the
|
||||||
|
# alias of an app.
|
||||||
|
KNOWN_TYPES = [
|
||||||
|
(("winword", "word"), "docx docm doc dotx dotm dot rtf odt"),
|
||||||
|
(("excel",), "xlsx xlsm xlsb xls xltx xltm csv ods"),
|
||||||
|
(("powerpnt", "powerpoint"), "pptx pptm ppt potx potm ppsx pps odp"),
|
||||||
|
(("onenote",), "one"),
|
||||||
|
(("outlook",), "msg"),
|
||||||
|
(("visio",), "vsdx vsd"),
|
||||||
|
(("msaccess", "access"), "accdb mdb"),
|
||||||
|
(("winproj", "project"), "mpp"),
|
||||||
|
(("mspub", "publisher"), "pub"),
|
||||||
|
(("acrobat", "acrord32", "acrord", "pdf24", "foxit"), "pdf"),
|
||||||
|
(("trueview", "dwgviewr", "autocad", "acad"), "dwg dxf"),
|
||||||
|
(("notepad",), "txt log"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class FeedError(Exception):
|
||||||
|
code = EXIT_NOFEED
|
||||||
|
|
||||||
|
|
||||||
|
class AuthError(FeedError):
|
||||||
|
code = EXIT_AUTH
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- NTLMv2 (MS-NLMP)
|
||||||
|
|
||||||
|
def md4(data):
|
||||||
|
"""hashlib has no MD4 any more on OpenSSL 3, and NTLM needs it."""
|
||||||
|
def rol(v, s):
|
||||||
|
v &= 0xFFFFFFFF
|
||||||
|
return ((v << s) | (v >> (32 - s))) & 0xFFFFFFFF
|
||||||
|
|
||||||
|
msg = bytearray(data) + b"\x80"
|
||||||
|
while len(msg) % 64 != 56:
|
||||||
|
msg.append(0)
|
||||||
|
msg += struct.pack("<Q", (8 * len(data)) & 0xFFFFFFFFFFFFFFFF)
|
||||||
|
a, b, c, d = 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476
|
||||||
|
for off in range(0, len(msg), 64):
|
||||||
|
x = struct.unpack("<16I", msg[off:off + 64])
|
||||||
|
aa, bb, cc, dd = a, b, c, d
|
||||||
|
for i in (0, 4, 8, 12):
|
||||||
|
a = rol(a + ((b & c) | (~b & d)) + x[i], 3)
|
||||||
|
d = rol(d + ((a & b) | (~a & c)) + x[i + 1], 7)
|
||||||
|
c = rol(c + ((d & a) | (~d & b)) + x[i + 2], 11)
|
||||||
|
b = rol(b + ((c & d) | (~c & a)) + x[i + 3], 19)
|
||||||
|
for i in (0, 1, 2, 3):
|
||||||
|
a = rol(a + ((b & c) | (b & d) | (c & d)) + x[i] + 0x5A827999, 3)
|
||||||
|
d = rol(d + ((a & b) | (a & c) | (b & c)) + x[i + 4] + 0x5A827999, 5)
|
||||||
|
c = rol(c + ((d & a) | (d & b) | (a & b)) + x[i + 8] + 0x5A827999, 9)
|
||||||
|
b = rol(b + ((c & d) | (c & a) | (d & a)) + x[i + 12] + 0x5A827999, 13)
|
||||||
|
for i in (0, 2, 1, 3):
|
||||||
|
a = rol(a + (b ^ c ^ d) + x[i] + 0x6ED9EBA1, 3)
|
||||||
|
d = rol(d + (a ^ b ^ c) + x[i + 8] + 0x6ED9EBA1, 9)
|
||||||
|
c = rol(c + (d ^ a ^ b) + x[i + 4] + 0x6ED9EBA1, 11)
|
||||||
|
b = rol(b + (c ^ d ^ a) + x[i + 12] + 0x6ED9EBA1, 15)
|
||||||
|
a, b = (a + aa) & 0xFFFFFFFF, (b + bb) & 0xFFFFFFFF
|
||||||
|
c, d = (c + cc) & 0xFFFFFFFF, (d + dd) & 0xFFFFFFFF
|
||||||
|
return struct.pack("<4I", a, b, c, d)
|
||||||
|
|
||||||
|
|
||||||
|
# UNICODE | REQUEST_TARGET | NTLM | ALWAYS_SIGN | EXTENDED_SESSIONSECURITY |
|
||||||
|
# TARGET_INFO | VERSION | 128 | 56
|
||||||
|
NTLM_FLAGS = (0x00000001 | 0x00000004 | 0x00000200 | 0x00008000 | 0x00080000
|
||||||
|
| 0x00800000 | 0x02000000 | 0x20000000 | 0x80000000)
|
||||||
|
NTLM_VERSION = bytes([10, 0, 0x61, 0x4A, 0, 0, 0, 15])
|
||||||
|
|
||||||
|
|
||||||
|
def ntlm_negotiate():
|
||||||
|
return (b"NTLMSSP\x00" + struct.pack("<II", 1, NTLM_FLAGS | 0x00000002)
|
||||||
|
+ struct.pack("<HHI", 0, 0, 40) * 2 + NTLM_VERSION)
|
||||||
|
|
||||||
|
|
||||||
|
def ntlm_av_pairs(info):
|
||||||
|
pairs, i = {}, 0
|
||||||
|
while i + 4 <= len(info):
|
||||||
|
av_id, length = struct.unpack("<HH", info[i:i + 4])
|
||||||
|
i += 4
|
||||||
|
if av_id == 0:
|
||||||
|
break
|
||||||
|
pairs[av_id] = info[i:i + length]
|
||||||
|
i += length
|
||||||
|
return pairs
|
||||||
|
|
||||||
|
|
||||||
|
def ntowfv2(user, domain, password):
|
||||||
|
return hmac.new(md4(password.encode("utf-16-le")),
|
||||||
|
(user.upper() + domain).encode("utf-16-le"), "md5").digest()
|
||||||
|
|
||||||
|
|
||||||
|
def ntlmv2_response(key, server_challenge, client_challenge, timestamp, target_info):
|
||||||
|
temp = (b"\x01\x01" + b"\x00" * 6 + timestamp + client_challenge + b"\x00" * 4
|
||||||
|
+ target_info + b"\x00" * 4)
|
||||||
|
return hmac.new(key, server_challenge + temp, "md5").digest() + temp
|
||||||
|
|
||||||
|
|
||||||
|
def ntlm_authenticate(challenge, user, domain, password):
|
||||||
|
"""The AUTHENTICATE message. domain None takes the server's domain."""
|
||||||
|
if challenge[:8] != b"NTLMSSP\x00" or struct.unpack("<I", challenge[8:12])[0] != 2:
|
||||||
|
raise FeedError("invalid NTLM challenge")
|
||||||
|
server_flags = struct.unpack("<I", challenge[20:24])[0]
|
||||||
|
server_challenge = challenge[24:32]
|
||||||
|
info_len, _, info_off = struct.unpack("<HHI", challenge[40:48])
|
||||||
|
target_info = challenge[info_off:info_off + info_len]
|
||||||
|
av = ntlm_av_pairs(target_info)
|
||||||
|
if domain is None:
|
||||||
|
domain = av.get(2, b"").decode("utf-16-le")
|
||||||
|
|
||||||
|
key = ntowfv2(user, domain, password)
|
||||||
|
client_challenge = os.urandom(8)
|
||||||
|
timestamp = av.get(7) or struct.pack("<Q", int((time.time() + 11644473600) * 10_000_000))
|
||||||
|
nt_response = ntlmv2_response(key, server_challenge, client_challenge, timestamp, target_info)
|
||||||
|
# With a server timestamp the LM response is left empty (MS-NLMP 3.1.5.1.2).
|
||||||
|
if 7 in av:
|
||||||
|
lm_response = b"\x00" * 24
|
||||||
|
else:
|
||||||
|
lm_response = hmac.new(key, server_challenge + client_challenge, "md5").digest() + client_challenge
|
||||||
|
|
||||||
|
workstation = socket.gethostname().split(".")[0].upper()
|
||||||
|
fields = [lm_response, nt_response, domain.encode("utf-16-le"), user.encode("utf-16-le"),
|
||||||
|
workstation.encode("utf-16-le"), b""]
|
||||||
|
offset, secbufs, payload = 72, b"", b""
|
||||||
|
for field in fields:
|
||||||
|
secbufs += struct.pack("<HHI", len(field), len(field), offset)
|
||||||
|
payload += field
|
||||||
|
offset += len(field)
|
||||||
|
message = (b"NTLMSSP\x00" + struct.pack("<I", 3) + secbufs
|
||||||
|
+ struct.pack("<I", server_flags & NTLM_FLAGS) + NTLM_VERSION + payload)
|
||||||
|
return message, domain
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- HTTP
|
||||||
|
|
||||||
|
class HiddenInputs(html.parser.HTMLParser):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.fields, self.action = {}, None
|
||||||
|
|
||||||
|
def handle_starttag(self, tag, attrs):
|
||||||
|
a = dict(attrs)
|
||||||
|
if tag == "form" and self.action is None:
|
||||||
|
self.action = a.get("action")
|
||||||
|
elif tag == "input" and (a.get("type") or "").lower() == "hidden" and a.get("name"):
|
||||||
|
self.fields[a["name"]] = a.get("value") or ""
|
||||||
|
|
||||||
|
|
||||||
|
class Session:
|
||||||
|
"""One keep-alive connection with cookies. NTLM signs in the connection,
|
||||||
|
not the request, so the handshake has to stay on one socket."""
|
||||||
|
|
||||||
|
def __init__(self, url, user, domain, password, timeout=30):
|
||||||
|
parts = urllib.parse.urlsplit(url)
|
||||||
|
self.scheme = parts.scheme
|
||||||
|
self.host = parts.hostname
|
||||||
|
self.port = parts.port
|
||||||
|
self.user, self.domain, self.password = user, domain, password
|
||||||
|
self.detected_domain = domain
|
||||||
|
self.timeout = timeout
|
||||||
|
self.cookies = {"TSWAFeatureCheckCookie": "true"}
|
||||||
|
self.conn = None
|
||||||
|
self.forms_tried = False
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
if self.conn:
|
||||||
|
self.conn.close()
|
||||||
|
self.conn = None
|
||||||
|
|
||||||
|
def _connect(self):
|
||||||
|
if self.scheme == "http":
|
||||||
|
return http.client.HTTPConnection(self.host, self.port, timeout=self.timeout)
|
||||||
|
return http.client.HTTPSConnection(self.host, self.port, timeout=self.timeout,
|
||||||
|
context=ssl.create_default_context())
|
||||||
|
|
||||||
|
def path(self, url, base="/"):
|
||||||
|
parts = urllib.parse.urlsplit(urllib.parse.urljoin(f"{self.scheme}://{self.host}{base}", url))
|
||||||
|
if parts.hostname and parts.hostname.lower() != self.host.lower():
|
||||||
|
raise FeedError(f"redirected to another server: {parts.hostname}")
|
||||||
|
return urllib.parse.urlunsplit(("", "", parts.path or "/", parts.query, ""))
|
||||||
|
|
||||||
|
def send(self, method, path, body=None, headers=None):
|
||||||
|
hdrs = {"User-Agent": "TSWorkspace/2.0", "Accept": "*/*", "Connection": "keep-alive"}
|
||||||
|
if self.cookies:
|
||||||
|
hdrs["Cookie"] = "; ".join(f"{k}={v}" for k, v in self.cookies.items())
|
||||||
|
hdrs.update(headers or {})
|
||||||
|
for attempt in range(2):
|
||||||
|
if self.conn is None:
|
||||||
|
self.conn = self._connect()
|
||||||
|
try:
|
||||||
|
self.conn.request(method, path, body=body, headers=hdrs)
|
||||||
|
resp = self.conn.getresponse()
|
||||||
|
data = resp.read()
|
||||||
|
break
|
||||||
|
except (http.client.HTTPException, OSError):
|
||||||
|
self.close()
|
||||||
|
if attempt:
|
||||||
|
raise
|
||||||
|
for name, value in resp.getheaders():
|
||||||
|
if name.lower() == "set-cookie":
|
||||||
|
cname, _, rest = value.partition("=")
|
||||||
|
cvalue = rest.split(";", 1)[0]
|
||||||
|
if cvalue and "01-jan-1970" not in value.lower():
|
||||||
|
self.cookies[cname.strip()] = cvalue
|
||||||
|
else:
|
||||||
|
self.cookies.pop(cname.strip(), None)
|
||||||
|
if (resp.getheader("Connection") or "").lower() == "close":
|
||||||
|
self.close()
|
||||||
|
return resp, data
|
||||||
|
|
||||||
|
def ntlm(self, method, path, body, headers):
|
||||||
|
auth = {"Authorization": "NTLM " + base64.b64encode(ntlm_negotiate()).decode()}
|
||||||
|
resp, data = self.send(method, path, body, {**(headers or {}), **auth})
|
||||||
|
challenge = next((v[5:] for k, v in resp.getheaders()
|
||||||
|
if k.lower() == "www-authenticate" and v.startswith("NTLM ")), None)
|
||||||
|
if resp.status != 401 or not challenge:
|
||||||
|
return resp, data
|
||||||
|
message, self.detected_domain = ntlm_authenticate(
|
||||||
|
base64.b64decode(challenge), self.user, self.domain, self.password)
|
||||||
|
auth = {"Authorization": "NTLM " + base64.b64encode(message).decode()}
|
||||||
|
return self.send(method, path, body, {**(headers or {}), **auth})
|
||||||
|
|
||||||
|
def fetch(self, url, method="GET", body=None, headers=None):
|
||||||
|
path = original = self.path(url)
|
||||||
|
token_retry = True
|
||||||
|
for _ in range(10):
|
||||||
|
resp, data = self.send(method, path, body, headers)
|
||||||
|
if resp.status == 401:
|
||||||
|
offered = " ".join(v.lower() for k, v in resp.getheaders()
|
||||||
|
if k.lower() == "www-authenticate")
|
||||||
|
if "ntlm" in offered:
|
||||||
|
resp, data = self.ntlm(method, path, body, headers)
|
||||||
|
if resp.status == 401:
|
||||||
|
raise AuthError("the server did not accept the user name or password")
|
||||||
|
content_type = (resp.getheader("Content-Type") or "").lower()
|
||||||
|
if resp.status == 200 and content_type.startswith("application/x-msts-webfeed-login"):
|
||||||
|
token = data.decode("ascii", "replace").strip()
|
||||||
|
for name in FEED_AUTH_COOKIES:
|
||||||
|
self.cookies[name] = token
|
||||||
|
if path != original and token_retry:
|
||||||
|
path, token_retry = original, False
|
||||||
|
continue
|
||||||
|
location = resp.getheader("Location")
|
||||||
|
if resp.status in (301, 302, 303, 307, 308) and location:
|
||||||
|
path = self.path(location, path)
|
||||||
|
if resp.status not in (307, 308):
|
||||||
|
method, body, headers = "GET", None, None
|
||||||
|
continue
|
||||||
|
return resp, data, path
|
||||||
|
raise FeedError("too many redirects")
|
||||||
|
|
||||||
|
def forms_login(self, login_path):
|
||||||
|
"""Some servers keep the .rdp files behind the RD Web form login."""
|
||||||
|
resp, data, path = self.fetch(login_path)
|
||||||
|
parser = HiddenInputs()
|
||||||
|
parser.feed(data.decode("utf-8", "replace"))
|
||||||
|
fields = dict(parser.fields)
|
||||||
|
user = self.user if "@" in self.user else f"{self.detected_domain}\\{self.user}"
|
||||||
|
fields.update({"DomainUserName": user, "UserPass": self.password,
|
||||||
|
"MachineType": "private", "isUtf8": "1"})
|
||||||
|
fields.setdefault("flags", "0")
|
||||||
|
action = self.path(parser.action or path, path)
|
||||||
|
resp, data, path = self.fetch(action, "POST", urllib.parse.urlencode(fields),
|
||||||
|
{"Content-Type": "application/x-www-form-urlencoded"})
|
||||||
|
if "login.aspx" in path.lower():
|
||||||
|
raise AuthError("the RD Web form login failed")
|
||||||
|
|
||||||
|
def download(self, url):
|
||||||
|
resp, data, path = self.fetch(url)
|
||||||
|
if "login.aspx" in path.lower() and not self.forms_tried:
|
||||||
|
self.forms_tried = True
|
||||||
|
self.forms_login(path)
|
||||||
|
resp, data, path = self.fetch(url)
|
||||||
|
if resp.status != 200:
|
||||||
|
raise FeedError(f"HTTP {resp.status}")
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- The feed
|
||||||
|
|
||||||
|
def local_tag(el):
|
||||||
|
return el.tag.rsplit("}", 1)[-1]
|
||||||
|
|
||||||
|
|
||||||
|
def make_slug(text, taken):
|
||||||
|
# ä → a, é → e: the marks go, the letters stay.
|
||||||
|
text = "".join(c for c in unicodedata.normalize("NFKD", text) if not unicodedata.combining(c))
|
||||||
|
base = re.sub(r"[^a-z0-9]+", "-", text.lower()).strip("-")[:40].strip("-") or "app"
|
||||||
|
slug, n = base, 2
|
||||||
|
while slug in taken:
|
||||||
|
slug, n = f"{base}-{n}", n + 1
|
||||||
|
taken.add(slug)
|
||||||
|
return slug
|
||||||
|
|
||||||
|
|
||||||
|
def common_suffix(names):
|
||||||
|
"""The end all aliases share, like "-RemoteApps-CmsRdsh", from a dash on."""
|
||||||
|
if len(names) < 2:
|
||||||
|
return ""
|
||||||
|
suffix = os.path.commonprefix([n[::-1] for n in names])[::-1]
|
||||||
|
cut = suffix.find("-")
|
||||||
|
suffix = suffix[cut:] if cut >= 0 else ""
|
||||||
|
return suffix if all(len(n) > len(suffix) for n in names) else ""
|
||||||
|
|
||||||
|
|
||||||
|
def parse_feed(data):
|
||||||
|
"""The workspace name and its apps. Raises FeedError if this is no feed."""
|
||||||
|
try:
|
||||||
|
root = ET.fromstring(data)
|
||||||
|
except ET.ParseError:
|
||||||
|
raise FeedError("the server sent no feed")
|
||||||
|
if local_tag(root) != "ResourceCollection":
|
||||||
|
raise FeedError("the server sent no feed")
|
||||||
|
name = ""
|
||||||
|
apps = []
|
||||||
|
for el in root.iter():
|
||||||
|
tag = local_tag(el)
|
||||||
|
if tag == "Publisher" and not name:
|
||||||
|
name = (el.get("Name") or "").strip()
|
||||||
|
if tag != "Resource":
|
||||||
|
continue
|
||||||
|
rtype = el.get("Type") or "RemoteApp"
|
||||||
|
if rtype not in ("RemoteApp", "Desktop"):
|
||||||
|
continue
|
||||||
|
title = (el.get("Title") or "").strip()
|
||||||
|
alias = el.get("Alias") or title or el.get("ID") or "app"
|
||||||
|
icons, rdp_url, types = [], None, []
|
||||||
|
for sub in el.iter():
|
||||||
|
stag = local_tag(sub)
|
||||||
|
if stag == "FileExtension" and sub.get("Name"):
|
||||||
|
ext = sub.get("Name").strip().lstrip(".").lower()
|
||||||
|
if re.fullmatch(r"[a-z0-9_+-]+", ext) and ext not in types:
|
||||||
|
types.append(ext)
|
||||||
|
elif stag.startswith("Icon") and sub.get("FileURL"):
|
||||||
|
dims = re.findall(r"\d+", sub.get("Dimensions") or stag) or ["0"]
|
||||||
|
icons.append({"url": sub.get("FileURL"), "type": (sub.get("FileType") or "").lower(),
|
||||||
|
"size": int(dims[0])})
|
||||||
|
elif stag == "ResourceFile" and sub.get("URL") and rdp_url is None:
|
||||||
|
if (sub.get("FileExtension") or ".rdp").lower() == ".rdp":
|
||||||
|
rdp_url = sub.get("URL")
|
||||||
|
if rdp_url:
|
||||||
|
apps.append({"alias": alias, "title": title or alias, "type": rtype,
|
||||||
|
"rdp": rdp_url, "icons": icons, "types": types})
|
||||||
|
suffix = common_suffix([a["alias"] for a in apps])
|
||||||
|
taken = set()
|
||||||
|
for app in apps:
|
||||||
|
app["slug"] = make_slug(app["alias"][:len(app["alias"]) - len(suffix)], taken)
|
||||||
|
return name, apps
|
||||||
|
|
||||||
|
|
||||||
|
def known_types(*names):
|
||||||
|
"""The file types of a well-known app, from its program, title or alias."""
|
||||||
|
text = " ".join(names).lower()
|
||||||
|
for keys, types in KNOWN_TYPES:
|
||||||
|
if any(re.search(rf"(^|[^a-z0-9]){re.escape(k)}($|[^a-z0-9])", text) for k in keys):
|
||||||
|
return types.split()
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- .rdp files
|
||||||
|
|
||||||
|
def decode_rdp(data):
|
||||||
|
if data.startswith(b"\xff\xfe") or data.startswith(b"\xfe\xff"):
|
||||||
|
return data.decode("utf-16")
|
||||||
|
if b"\x00" in data[:16]:
|
||||||
|
return data.decode("utf-16-le")
|
||||||
|
try:
|
||||||
|
return data.decode("utf-8-sig")
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
return data.decode("latin-1")
|
||||||
|
|
||||||
|
|
||||||
|
def rdp_set(text, name, kind, value):
|
||||||
|
pattern = re.compile(rf"^{re.escape(name)}:[isb]:[^\r\n]*", re.IGNORECASE | re.MULTILINE)
|
||||||
|
line = f"{name}:{kind}:{value}"
|
||||||
|
if pattern.search(text):
|
||||||
|
return pattern.sub(line, text)
|
||||||
|
return text.rstrip("\r\n") + "\r\n" + line + "\r\n"
|
||||||
|
|
||||||
|
|
||||||
|
def rdp_del(text, name):
|
||||||
|
return re.sub(rf"^{re.escape(name)}:[isb]:[^\r\n]*\r?\n?", "", text, flags=re.IGNORECASE | re.MULTILINE)
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_rdp(text):
|
||||||
|
# One sign-in for the gateway and the session, and never a prompt: started
|
||||||
|
# from the app menu, there is no terminal to ask in.
|
||||||
|
text = rdp_set(text, "promptcredentialonce", "i", "1")
|
||||||
|
text = rdp_set(text, "prompt for credentials on client", "i", "0")
|
||||||
|
# Drive letters mean nothing here, and FreeRDP reads "*" as every mount,
|
||||||
|
# camera or USB device of this computer. Sharing a folder is a setting.
|
||||||
|
for name in ("drivestoredirect", "devicestoredirect", "camerastoredirect", "usbdevicestoredirect"):
|
||||||
|
text = rdp_del(text, name)
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- Icons
|
||||||
|
|
||||||
|
def png_encode(width, height, rows):
|
||||||
|
"""RGBA rows, top down, as a PNG."""
|
||||||
|
def chunk(kind, payload):
|
||||||
|
return (struct.pack(">I", len(payload)) + kind + payload
|
||||||
|
+ struct.pack(">I", zlib.crc32(kind + payload) & 0xFFFFFFFF))
|
||||||
|
raw = b"".join(b"\x00" + bytes(row) for row in rows)
|
||||||
|
return (PNG_SIGNATURE + chunk(b"IHDR", struct.pack(">IIBBBBB", width, height, 8, 6, 0, 0, 0))
|
||||||
|
+ chunk(b"IDAT", zlib.compress(raw, 9)) + chunk(b"IEND", b""))
|
||||||
|
|
||||||
|
|
||||||
|
def dib_to_png(dib):
|
||||||
|
"""One bitmap frame of an .ico (BITMAPINFOHEADER, 1 to 32 bits) as a PNG."""
|
||||||
|
header, width, height2, _, bpp, compression = struct.unpack("<IiiHHI", dib[:20])
|
||||||
|
height = abs(height2) // 2
|
||||||
|
if width <= 0 or height <= 0 or compression not in (0, 3) or bpp not in (1, 4, 8, 24, 32):
|
||||||
|
return None
|
||||||
|
off = header + (12 if compression == 3 else 0)
|
||||||
|
palette = []
|
||||||
|
if bpp <= 8:
|
||||||
|
count = struct.unpack("<I", dib[32:36])[0] or (1 << bpp)
|
||||||
|
for i in range(count):
|
||||||
|
b, g, r = dib[off + 4 * i:off + 4 * i + 3]
|
||||||
|
palette.append((r, g, b))
|
||||||
|
off += 4 * count
|
||||||
|
stride = ((width * bpp + 31) // 32) * 4
|
||||||
|
pixels = dib[off:off + stride * height]
|
||||||
|
mask_stride = ((width + 31) // 32) * 4
|
||||||
|
mask = dib[off + stride * height:off + stride * height + mask_stride * height]
|
||||||
|
alpha = bpp == 32 and any(pixels[i] for i in range(3, len(pixels), 4))
|
||||||
|
|
||||||
|
rows = []
|
||||||
|
for y in range(height):
|
||||||
|
src = height - 1 - y
|
||||||
|
row = pixels[src * stride:(src + 1) * stride]
|
||||||
|
mrow = mask[src * mask_stride:(src + 1) * mask_stride]
|
||||||
|
out = bytearray()
|
||||||
|
for x in range(width):
|
||||||
|
if bpp == 32:
|
||||||
|
b, g, r, a = row[4 * x:4 * x + 4]
|
||||||
|
if not alpha:
|
||||||
|
a = 255
|
||||||
|
elif bpp == 24:
|
||||||
|
b, g, r = row[3 * x:3 * x + 3]
|
||||||
|
a = 255
|
||||||
|
else:
|
||||||
|
bit = x * bpp
|
||||||
|
index = (row[bit // 8] >> (8 - bpp - bit % 8)) & ((1 << bpp) - 1)
|
||||||
|
r, g, b = palette[index] if index < len(palette) else (0, 0, 0)
|
||||||
|
a = 255
|
||||||
|
if not alpha and x // 8 < len(mrow) and (mrow[x // 8] >> (7 - x % 8)) & 1:
|
||||||
|
a = 0
|
||||||
|
out += bytes((r, g, b, a))
|
||||||
|
rows.append(out)
|
||||||
|
return png_encode(width, height, rows)
|
||||||
|
|
||||||
|
|
||||||
|
def ico_to_png(data):
|
||||||
|
"""The biggest frame of an .ico, as a PNG. None if there is none to read."""
|
||||||
|
if data[:4] != b"\x00\x00\x01\x00":
|
||||||
|
return None
|
||||||
|
count = struct.unpack("<H", data[4:6])[0]
|
||||||
|
entries = []
|
||||||
|
for i in range(count):
|
||||||
|
entry = data[6 + 16 * i:22 + 16 * i]
|
||||||
|
if len(entry) < 16:
|
||||||
|
break
|
||||||
|
w, h, _, _, _, bpp, size, offset = struct.unpack("<BBBBHHII", entry)
|
||||||
|
entries.append(((w or 256) * (h or 256), bpp, size, offset))
|
||||||
|
for _, _, size, offset in sorted(entries, reverse=True):
|
||||||
|
frame = data[offset:offset + size]
|
||||||
|
if frame[:8] == PNG_SIGNATURE:
|
||||||
|
return frame
|
||||||
|
try:
|
||||||
|
png = dib_to_png(frame)
|
||||||
|
except (struct.error, ValueError, IndexError):
|
||||||
|
png = None
|
||||||
|
if png:
|
||||||
|
return png
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def save_icon(session, app, dest):
|
||||||
|
candidates = [i for i in app["icons"] if i["type"] == "ico"]
|
||||||
|
candidates += sorted((i for i in app["icons"] if i["type"] != "ico"),
|
||||||
|
key=lambda i: i["size"], reverse=True)
|
||||||
|
for icon in candidates:
|
||||||
|
try:
|
||||||
|
data = session.download(icon["url"])
|
||||||
|
except (FeedError, OSError, http.client.HTTPException):
|
||||||
|
continue
|
||||||
|
png = data if data[:8] == PNG_SIGNATURE else ico_to_png(data)
|
||||||
|
if png:
|
||||||
|
with open(dest, "wb") as f:
|
||||||
|
f.write(png)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- Main
|
||||||
|
|
||||||
|
def say(*fields):
|
||||||
|
print("\t".join(str(f).replace("\t", " ").replace("\n", " ") for f in fields), flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
def run(args, password):
|
||||||
|
session = Session(args.url, args.user, args.domain, password, args.timeout)
|
||||||
|
resp, data, _ = session.fetch(args.url)
|
||||||
|
if resp.status != 200:
|
||||||
|
raise FeedError(f"HTTP {resp.status}")
|
||||||
|
name, apps = parse_feed(data)
|
||||||
|
|
||||||
|
os.makedirs(os.path.join(args.out, "rdp"), exist_ok=True)
|
||||||
|
os.makedirs(os.path.join(args.out, "icons"), exist_ok=True)
|
||||||
|
with open(os.path.join(args.out, "feed.xml"), "wb") as f:
|
||||||
|
f.write(data)
|
||||||
|
say("workspace", name or session.host)
|
||||||
|
say("domain", session.detected_domain or "")
|
||||||
|
|
||||||
|
done = []
|
||||||
|
for app in apps:
|
||||||
|
try:
|
||||||
|
text = decode_rdp(session.download(app["rdp"]))
|
||||||
|
if "full address:s:" not in text.lower():
|
||||||
|
raise FeedError("not an RDP file")
|
||||||
|
with open(os.path.join(args.out, "rdp", app["slug"] + ".rdp"), "w") as f:
|
||||||
|
f.write(sanitize_rdp(text))
|
||||||
|
if not app["types"] and app["type"] == "RemoteApp":
|
||||||
|
program = re.search(r"^remoteapplicationprogram:s:(.*)$", text, re.IGNORECASE | re.MULTILINE)
|
||||||
|
app["types"] = known_types(program.group(1) if program else "", app["title"], app["alias"])
|
||||||
|
save_icon(session, app, os.path.join(args.out, "icons", app["slug"] + ".png"))
|
||||||
|
except (FeedError, OSError, http.client.HTTPException) as e:
|
||||||
|
say("skip", app["title"], e)
|
||||||
|
continue
|
||||||
|
done.append(app)
|
||||||
|
say("app", app["slug"], app["type"], app["title"])
|
||||||
|
|
||||||
|
with open(os.path.join(args.out, "apps.tsv"), "w") as f:
|
||||||
|
for app in done:
|
||||||
|
f.write("\t".join((app["slug"], app["type"], app["title"].replace("\t", " "),
|
||||||
|
" ".join(app["types"]))) + "\n")
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
parser = argparse.ArgumentParser(prog="rdfeed-fetch", description="Download the apps of an RD Web Access feed.")
|
||||||
|
parser.add_argument("--url", required=True)
|
||||||
|
parser.add_argument("--user", required=True)
|
||||||
|
parser.add_argument("--domain", help="Windows domain; without it the server's own is used")
|
||||||
|
parser.add_argument("--out", required=True, help="directory for the downloaded files")
|
||||||
|
parser.add_argument("--timeout", type=float, default=30)
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
|
password = sys.stdin.readline().rstrip("\n")
|
||||||
|
try:
|
||||||
|
run(args, password)
|
||||||
|
except FeedError as e:
|
||||||
|
print(f"rdfeed-fetch: {e}", file=sys.stderr)
|
||||||
|
return e.code
|
||||||
|
except (OSError, http.client.HTTPException) as e:
|
||||||
|
print(f"rdfeed-fetch: {e}", file=sys.stderr)
|
||||||
|
return EXIT_NETWORK
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main(sys.argv[1:]))
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
/*
|
||||||
|
* rdfeed-hook: works around two FreeRDP problems, loaded with LD_PRELOAD.
|
||||||
|
*
|
||||||
|
* 1. The second MFA prompt. A connection broker sends the client on to the
|
||||||
|
* server that hosts the session, so FreeRDP opens a second tunnel through
|
||||||
|
* the RD Gateway right away. With MFA on the gateway (the Azure MFA NPS
|
||||||
|
* extension), that second sign-in needs a second prompt. When it comes
|
||||||
|
* less than about 10 seconds after the first one was confirmed, no prompt
|
||||||
|
* is sent and the gateway waits forever. Windows does not run into this.
|
||||||
|
* So a new connection to port 443 waits a little first.
|
||||||
|
*
|
||||||
|
* RDFEED_GW_WAIT auto (the default), always or never. auto waits only
|
||||||
|
* when the first sign-in took long enough for somebody to
|
||||||
|
* confirm a prompt.
|
||||||
|
* RDFEED_GW_DELAY how long to wait, in seconds (12).
|
||||||
|
*
|
||||||
|
* Before waiting it prints "[rdfeed] gateway-delay <seconds>" to stderr,
|
||||||
|
* so rdfeed can tell the user a second prompt is coming.
|
||||||
|
*
|
||||||
|
* 2. The graphics pipeline. FreeRDP 3 always turns GFX on, there is no switch
|
||||||
|
* to turn it off, and in RemoteApp mode xfreerdp does not repaint a window
|
||||||
|
* after it was minimized or resized. With RDFEED_NO_GFX set, every attempt
|
||||||
|
* to turn it on is turned into off, and the classic drawing is used.
|
||||||
|
*
|
||||||
|
* It also makes stdout line buffered, so FreeRDP's log reaches rdfeed as it
|
||||||
|
* happens.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 Felitendo
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define _GNU_SOURCE
|
||||||
|
#include <dlfcn.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/* A pause this long between two connections means a new tunnel, not the
|
||||||
|
* second channel of the same one or an IPv6 to IPv4 fallback. */
|
||||||
|
#define NEW_TUNNEL_SECONDS 2.0
|
||||||
|
/* A first sign-in that took this long had a person confirming a prompt. */
|
||||||
|
#define MFA_SECONDS 2.5
|
||||||
|
|
||||||
|
static int (*real_connect)(int, const struct sockaddr *, socklen_t);
|
||||||
|
static int (*real_set_bool)(void *, int, int);
|
||||||
|
|
||||||
|
static double first_connect = -1;
|
||||||
|
static double last_connect = -1;
|
||||||
|
|
||||||
|
static double now(void)
|
||||||
|
{
|
||||||
|
struct timespec ts;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
|
return (double)ts.tv_sec + (double)ts.tv_nsec / 1e9;
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((constructor)) static void init(void)
|
||||||
|
{
|
||||||
|
setvbuf(stdout, NULL, _IOLBF, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RTLD_NEXT only finds libfreerdp3 when it was loaded globally, as it is in
|
||||||
|
* xfreerdp. Otherwise ask the library itself. */
|
||||||
|
static void *freerdp_sym(const char *name)
|
||||||
|
{
|
||||||
|
void *sym = dlsym(RTLD_NEXT, name);
|
||||||
|
if (!sym) {
|
||||||
|
void *lib = dlopen("libfreerdp3.so.3", RTLD_LAZY | RTLD_NOLOAD);
|
||||||
|
if (lib)
|
||||||
|
sym = dlsym(lib, name);
|
||||||
|
}
|
||||||
|
return sym;
|
||||||
|
}
|
||||||
|
|
||||||
|
static long gfx_key(void)
|
||||||
|
{
|
||||||
|
static long key = -2;
|
||||||
|
if (key == -2) {
|
||||||
|
key = -1;
|
||||||
|
if (getenv("RDFEED_NO_GFX")) {
|
||||||
|
ssize_t (*by_name)(const char *) =
|
||||||
|
(ssize_t (*)(const char *))freerdp_sym("freerdp_settings_get_key_for_name");
|
||||||
|
if (by_name)
|
||||||
|
key = (long)by_name("FreeRDP_SupportGraphicsPipeline");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
int freerdp_settings_set_bool(void *settings, int id, int val)
|
||||||
|
{
|
||||||
|
if (!real_set_bool)
|
||||||
|
real_set_bool = (int (*)(void *, int, int))freerdp_sym("freerdp_settings_set_bool");
|
||||||
|
if (!real_set_bool)
|
||||||
|
return 0;
|
||||||
|
if (val && id == gfx_key())
|
||||||
|
val = 0;
|
||||||
|
return real_set_bool(settings, id, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int should_wait(double t)
|
||||||
|
{
|
||||||
|
const char *mode = getenv("RDFEED_GW_WAIT");
|
||||||
|
|
||||||
|
if (last_connect < 0 || t - last_connect < NEW_TUNNEL_SECONDS)
|
||||||
|
return 0;
|
||||||
|
if (mode && strcmp(mode, "never") == 0)
|
||||||
|
return 0;
|
||||||
|
if (mode && strcmp(mode, "always") == 0)
|
||||||
|
return 1;
|
||||||
|
return t - first_connect >= MFA_SECONDS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int connect(int fd, const struct sockaddr *addr, socklen_t len)
|
||||||
|
{
|
||||||
|
if (!real_connect)
|
||||||
|
real_connect = (int (*)(int, const struct sockaddr *, socklen_t))dlsym(RTLD_NEXT, "connect");
|
||||||
|
|
||||||
|
int port = -1;
|
||||||
|
if (addr && addr->sa_family == AF_INET)
|
||||||
|
port = ntohs(((const struct sockaddr_in *)addr)->sin_port);
|
||||||
|
else if (addr && addr->sa_family == AF_INET6)
|
||||||
|
port = ntohs(((const struct sockaddr_in6 *)addr)->sin6_port);
|
||||||
|
|
||||||
|
if (port == 443) {
|
||||||
|
double t = now();
|
||||||
|
if (first_connect < 0)
|
||||||
|
first_connect = t;
|
||||||
|
if (should_wait(t)) {
|
||||||
|
const char *env = getenv("RDFEED_GW_DELAY");
|
||||||
|
unsigned left = env ? (unsigned)atoi(env) : 12;
|
||||||
|
fprintf(stderr, "[rdfeed] gateway-delay %u\n", left);
|
||||||
|
fflush(stderr);
|
||||||
|
while (left)
|
||||||
|
left = sleep(left);
|
||||||
|
t = now();
|
||||||
|
}
|
||||||
|
last_connect = t;
|
||||||
|
}
|
||||||
|
return real_connect(fd, addr, len);
|
||||||
|
}
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
# shellcheck shell=bash
|
||||||
|
#
|
||||||
|
# Paths, translations and output helpers.
|
||||||
|
#
|
||||||
|
# Everything rdfeed keeps is the user's own: a settings file and one file per
|
||||||
|
# workspace under ~/.config/rdfeed, the downloaded apps under
|
||||||
|
# ~/.local/share/rdfeed, and the passwords in the keyring. Nothing needs root.
|
||||||
|
|
||||||
|
RF_VERSION="@VERSION@"
|
||||||
|
RF_NAME="rdfeed"
|
||||||
|
RF_PRETTY="rdfeed"
|
||||||
|
|
||||||
|
RF_LIBDIR="${RF_LIBDIR:-@LIBDIR@}"
|
||||||
|
RF_LIBEXECDIR="${RF_LIBEXECDIR:-@LIBEXECDIR@}"
|
||||||
|
RF_LOCALEDIR="${RF_LOCALEDIR:-@LOCALEDIR@}"
|
||||||
|
|
||||||
|
RF_FETCH="${RF_FETCH:-$RF_LIBEXECDIR/rdfeed-fetch}"
|
||||||
|
RF_HOOK="${RF_HOOK:-$RF_LIBEXECDIR/rdfeed-hook.so}"
|
||||||
|
|
||||||
|
RF_XDG_CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}"
|
||||||
|
RF_XDG_DATA="${XDG_DATA_HOME:-$HOME/.local/share}"
|
||||||
|
RF_XDG_CACHE="${XDG_CACHE_HOME:-$HOME/.cache}"
|
||||||
|
|
||||||
|
RF_CONFDIR="${RF_XDG_CONFIG}/${RF_NAME}"
|
||||||
|
RF_CONFIG="${RF_CONFDIR}/config"
|
||||||
|
RF_WSDIR="${RF_CONFDIR}/workspaces"
|
||||||
|
RF_DATADIR="${RF_XDG_DATA}/${RF_NAME}"
|
||||||
|
RF_CACHEDIR="${RF_XDG_CACHE}/${RF_NAME}"
|
||||||
|
# For what only lives while an app runs: the .rdp copy that names a file.
|
||||||
|
RF_RUNDIR="${XDG_RUNTIME_DIR:-$RF_CACHEDIR}/${RF_NAME}"
|
||||||
|
|
||||||
|
# Where the app menu looks: the entries, the folder for each workspace (a
|
||||||
|
# merged menu) and the folder's name and icon.
|
||||||
|
RF_APPDIR="${RF_XDG_DATA}/applications"
|
||||||
|
RF_MENUDIR="${RF_XDG_CONFIG}/menus/applications-merged"
|
||||||
|
RF_DIRDIR="${RF_XDG_DATA}/desktop-directories"
|
||||||
|
|
||||||
|
RF_UNIT_TIMER="rdfeed-refresh.timer"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Translations
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export TEXTDOMAIN="rdfeed"
|
||||||
|
export TEXTDOMAINDIR="${RF_LOCALEDIR}"
|
||||||
|
|
||||||
|
rf_ui_locale() {
|
||||||
|
local l="${RF_UI_LOCALE:-}"
|
||||||
|
|
||||||
|
if [[ -z $l ]]; then
|
||||||
|
l="${LC_ALL:-}"
|
||||||
|
[[ -z $l ]] && l="${LC_MESSAGES:-}"
|
||||||
|
[[ -z $l ]] && l="${LANG:-}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# systemd writes /etc/locale.conf and most distributions use it; Debian and
|
||||||
|
# Ubuntu keep the same LANG= line in /etc/default/locale instead.
|
||||||
|
if [[ -z $l ]]; then
|
||||||
|
local f
|
||||||
|
for f in /etc/locale.conf /etc/default/locale; do
|
||||||
|
[[ -r $f ]] || continue
|
||||||
|
l="$(sed -n 's/^LANG=//p' "$f" | tr -d '"' | head -n1)"
|
||||||
|
[[ -n $l ]] && break
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n' "${l:-C}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Every gettext lookup is a fork and the settings screen redraws a screenful of
|
||||||
|
# labels per keypress, so results are memoized.
|
||||||
|
declare -A RF_MSG_CACHE=()
|
||||||
|
|
||||||
|
RF_MSG_RESULT=''
|
||||||
|
|
||||||
|
# rf_msg_into <locale> <msgid>
|
||||||
|
# Plain lookup with the result in RF_MSG_RESULT and no printf formatting, for
|
||||||
|
# callers that would otherwise pay a fork per label per frame.
|
||||||
|
rf_msg_into() {
|
||||||
|
local locale="$1" msgid="$2" cachekey
|
||||||
|
cachekey="${locale}"$'\x1f'"${msgid}"
|
||||||
|
|
||||||
|
if [[ -n ${RF_MSG_CACHE[$cachekey]+set} ]]; then
|
||||||
|
RF_MSG_RESULT="${RF_MSG_CACHE[$cachekey]}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
RF_MSG_RESULT="$(LC_ALL="$locale" LANGUAGE="${locale%%.*}" gettext -- "$msgid" 2>/dev/null)"
|
||||||
|
[[ -n $RF_MSG_RESULT ]] || RF_MSG_RESULT="$msgid"
|
||||||
|
RF_MSG_CACHE[$cachekey]="$RF_MSG_RESULT"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# rf_msg_in <locale> <msgid> [printf args...]
|
||||||
|
rf_msg_in() {
|
||||||
|
local locale="$1" msgid="$2"
|
||||||
|
shift 2
|
||||||
|
|
||||||
|
rf_msg_into "$locale" "$msgid"
|
||||||
|
|
||||||
|
# With no arguments the message is plain text, not a format string. Feeding
|
||||||
|
# it to printf anyway would turn a literal percent sign in a translation
|
||||||
|
# into an invalid conversion.
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
printf '%s' "$RF_MSG_RESULT"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# shellcheck disable=SC2059 # the format string is the translated message
|
||||||
|
printf -- "$RF_MSG_RESULT" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
RF_LOCALE_CACHED=''
|
||||||
|
|
||||||
|
# rf_msg <msgid> [printf args...]
|
||||||
|
rf_msg() {
|
||||||
|
[[ -n $RF_LOCALE_CACHED ]] || RF_LOCALE_CACHED="$(rf_ui_locale)"
|
||||||
|
rf_msg_in "$RF_LOCALE_CACHED" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Output
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Decided once, while stdout is still whatever the process was started with:
|
||||||
|
# testing -t 1 at the point of use is wrong for anything called through $(...),
|
||||||
|
# which sees a pipe and would conclude nobody is watching.
|
||||||
|
RF_INTERACTIVE=''
|
||||||
|
[[ -t 1 ]] && RF_INTERACTIVE=1
|
||||||
|
|
||||||
|
if [[ -n $RF_INTERACTIVE && -z ${NO_COLOR:-} ]]; then
|
||||||
|
RF_C_RESET=$'\033[0m'
|
||||||
|
RF_C_BOLD=$'\033[1m'
|
||||||
|
RF_C_DIM=$'\033[2m'
|
||||||
|
RF_C_BLUE=$'\033[38;2;52;153;255m'
|
||||||
|
RF_C_GREEN=$'\033[32m'
|
||||||
|
RF_C_YELLOW=$'\033[33m'
|
||||||
|
RF_C_RED=$'\033[31m'
|
||||||
|
else
|
||||||
|
RF_C_RESET='' RF_C_BOLD='' RF_C_DIM='' RF_C_BLUE=''
|
||||||
|
RF_C_GREEN='' RF_C_YELLOW='' RF_C_RED=''
|
||||||
|
fi
|
||||||
|
|
||||||
|
# What rf_ok, rf_bad and rf_note printed. The menu redraws straight after an
|
||||||
|
# action, which wipes the screen, so it shows these again under the new frame
|
||||||
|
# rather than holding everything up for a key press.
|
||||||
|
RF_UI_NOTICES=()
|
||||||
|
|
||||||
|
rf_say() { printf '%s\n' "$*"; }
|
||||||
|
rf_head() { printf '\n%s%s%s\n\n' "$RF_C_BOLD$RF_C_BLUE" "$*" "$RF_C_RESET"; }
|
||||||
|
rf_ok() { RF_UI_NOTICES+=("$RF_C_GREEN✔$RF_C_RESET $*"); printf '%s✔%s %s\n' "$RF_C_GREEN" "$RF_C_RESET" "$*"; }
|
||||||
|
rf_bad() { RF_UI_NOTICES+=("$RF_C_RED✘$RF_C_RESET $*"); printf '%s✘%s %s\n' "$RF_C_RED" "$RF_C_RESET" "$*" >&2; }
|
||||||
|
rf_note() { RF_UI_NOTICES+=("$RF_C_DIM•$RF_C_RESET $*"); printf '%s•%s %s\n' "$RF_C_DIM" "$RF_C_RESET" "$*"; }
|
||||||
|
|
||||||
|
rf_have() { command -v "$1" > /dev/null 2>&1; }
|
||||||
|
|
||||||
|
# rf_notify <summary> <body> [icon]
|
||||||
|
# A desktop notification, for what happens after the menu or the app menu
|
||||||
|
# started something and nobody watches a terminal.
|
||||||
|
rf_notify() {
|
||||||
|
rf_have notify-send || return 0
|
||||||
|
notify-send -a "$RF_PRETTY" -i "${3:-rdfeed}" -- "$1" "$2" > /dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Human-readable "x minutes ago" for a unix timestamp. 0 or empty yields the
|
||||||
|
# translated "never".
|
||||||
|
#
|
||||||
|
# Written with [[ ]] and a variable for each number on purpose: xgettext reads
|
||||||
|
# the < of an (( )) as a redirection and loses every string after it.
|
||||||
|
rf_time_ago() {
|
||||||
|
local ts="$1" now delta n
|
||||||
|
|
||||||
|
if [[ ! $ts =~ ^[0-9]+$ || $ts -eq 0 ]]; then
|
||||||
|
rf_msg "never"
|
||||||
|
printf '\n'
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
now="$(date +%s)"
|
||||||
|
delta=$(( now - ts ))
|
||||||
|
[[ $delta -lt 0 ]] && delta=0
|
||||||
|
|
||||||
|
if [[ $delta -lt 60 ]]; then
|
||||||
|
rf_msg "just now"
|
||||||
|
elif [[ $delta -lt 120 ]]; then
|
||||||
|
rf_msg "1 minute ago"
|
||||||
|
elif [[ $delta -lt 3600 ]]; then
|
||||||
|
n=$(( delta / 60 ))
|
||||||
|
rf_msg "%d minutes ago" "$n"
|
||||||
|
elif [[ $delta -lt 7200 ]]; then
|
||||||
|
rf_msg "1 hour ago"
|
||||||
|
elif [[ $delta -lt 86400 ]]; then
|
||||||
|
n=$(( delta / 3600 ))
|
||||||
|
rf_msg "%d hours ago" "$n"
|
||||||
|
elif [[ $delta -lt 172800 ]]; then
|
||||||
|
rf_msg "1 day ago"
|
||||||
|
else
|
||||||
|
n=$(( delta / 86400 ))
|
||||||
|
rf_msg "%d days ago" "$n"
|
||||||
|
fi
|
||||||
|
printf '\n'
|
||||||
|
}
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
# shellcheck shell=bash
|
||||||
|
#
|
||||||
|
# Reading and writing the settings files.
|
||||||
|
#
|
||||||
|
# ~/.config/rdfeed/config holds how the apps run (a shared folder, the size,
|
||||||
|
# the graphics), and ~/.config/rdfeed/workspaces/<id> where a workspace comes
|
||||||
|
# from and who signs in to it. Neither is meant to be edited by hand: every
|
||||||
|
# option is in the menu.
|
||||||
|
#
|
||||||
|
# Both are parsed rather than sourced. One "Key=Value" per line, '#' comments.
|
||||||
|
|
||||||
|
declare -A RF_KV_CACHE=()
|
||||||
|
|
||||||
|
# _rf_kv_lookup <file> <Key> [default]
|
||||||
|
# Result in RF_KV_VALUE. Assigning rather than printing matters on the
|
||||||
|
# settings screen, which reads every key on every frame: a command
|
||||||
|
# substitution there is a fork, and forks are the whole cost of a redraw.
|
||||||
|
RF_KV_VALUE=''
|
||||||
|
|
||||||
|
_rf_kv_lookup() {
|
||||||
|
local file="$1" key="$2" default="${3:-}" val='' line content
|
||||||
|
|
||||||
|
RF_KV_VALUE="$default"
|
||||||
|
[[ -r $file ]] || return 0
|
||||||
|
|
||||||
|
if [[ -n ${RF_KV_CACHE[$file]+set} ]]; then
|
||||||
|
content="${RF_KV_CACHE[$file]}"
|
||||||
|
else
|
||||||
|
content="$(< "$file")"
|
||||||
|
RF_KV_CACHE[$file]="$content"
|
||||||
|
fi
|
||||||
|
|
||||||
|
while IFS= read -r line; do
|
||||||
|
[[ $line == *"$key"* ]] || continue
|
||||||
|
[[ $line =~ ^[[:space:]]*"$key"[[:space:]]*=(.*)$ ]] || continue
|
||||||
|
val="${BASH_REMATCH[1]}"
|
||||||
|
done <<< "$content"
|
||||||
|
|
||||||
|
val="${val#"${val%%[![:space:]]*}"}"
|
||||||
|
val="${val%"${val##*[![:space:]]}"}"
|
||||||
|
|
||||||
|
[[ -n $val ]] && RF_KV_VALUE="$val"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
rf_is_true() {
|
||||||
|
case "${1,,}" in
|
||||||
|
yes|y|true|1|on|enabled) return 0 ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# rf_kv_set <file> <Key> <Value> <header line>
|
||||||
|
rf_kv_set() {
|
||||||
|
local file="$1" key="$2" value="$3" header="$4" tmp
|
||||||
|
|
||||||
|
if [[ ! -e $file ]]; then
|
||||||
|
mkdir -p "$(dirname "$file")" || return 1
|
||||||
|
{
|
||||||
|
printf '# %s\n' "$header"
|
||||||
|
printf '#\n'
|
||||||
|
# shellcheck disable=SC2016 # the backticks are text
|
||||||
|
printf '# Written by `%s`. Nothing here needs editing by hand:\n' "$RF_NAME"
|
||||||
|
printf '# every option is in the menu.\n'
|
||||||
|
} > "$file" || return 1
|
||||||
|
fi
|
||||||
|
[[ -w $file ]] || return 1
|
||||||
|
|
||||||
|
tmp="$(mktemp "${file}.XXXXXX")" || return 1
|
||||||
|
chmod --reference="$file" "$tmp" 2>/dev/null || chmod 0644 "$tmp"
|
||||||
|
|
||||||
|
if grep -qE "^[[:space:]]*#?[[:space:]]*${key}[[:space:]]*=" "$file"; then
|
||||||
|
awk -v key="$key" -v value="$value" '
|
||||||
|
!done && $0 ~ "^[[:space:]]*#?[[:space:]]*" key "[[:space:]]*=" {
|
||||||
|
print key "=" value; done = 1; next
|
||||||
|
}
|
||||||
|
# drop any further occurrences so the file cannot grow duplicates
|
||||||
|
$0 ~ "^[[:space:]]*" key "[[:space:]]*=" { next }
|
||||||
|
{ print }
|
||||||
|
' "$file" > "$tmp" || { rm -f "$tmp"; return 1; }
|
||||||
|
else
|
||||||
|
cat "$file" > "$tmp" || { rm -f "$tmp"; return 1; }
|
||||||
|
printf '%s=%s\n' "$key" "$value" >> "$tmp"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mv -f "$tmp" "$file"
|
||||||
|
unset 'RF_KV_CACHE[$file]'
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# The settings
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
rf_config_get() {
|
||||||
|
_rf_kv_lookup "$RF_CONFIG" "$@"
|
||||||
|
printf '%s\n' "$RF_KV_VALUE"
|
||||||
|
}
|
||||||
|
|
||||||
|
rf_config_set() {
|
||||||
|
rf_kv_set "$RF_CONFIG" "$1" "$2" "$RF_PRETTY"
|
||||||
|
}
|
||||||
|
|
||||||
|
# rf_config_load
|
||||||
|
# Everything starting an app needs, resolved once.
|
||||||
|
rf_config_load() {
|
||||||
|
RF_KV_CACHE=()
|
||||||
|
_rf_kv_lookup "$RF_CONFIG" Share off; CFG_SHARE="$RF_KV_VALUE"
|
||||||
|
_rf_kv_lookup "$RF_CONFIG" Scale auto; CFG_SCALE="$RF_KV_VALUE"
|
||||||
|
_rf_kv_lookup "$RF_CONFIG" Graphics gfx; CFG_GRAPHICS="$RF_KV_VALUE"
|
||||||
|
_rf_kv_lookup "$RF_CONFIG" MfaWait auto; CFG_MFAWAIT="$RF_KV_VALUE"
|
||||||
|
_rf_kv_lookup "$RF_CONFIG" Sound yes; CFG_SOUND=no; rf_is_true "$RF_KV_VALUE" && CFG_SOUND=yes
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Workspaces
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
rf_ws_file() { printf '%s/%s\n' "$RF_WSDIR" "$1"; }
|
||||||
|
|
||||||
|
# rf_ws_ids
|
||||||
|
# The workspaces, sorted, in RF_WS_IDS.
|
||||||
|
RF_WS_IDS=()
|
||||||
|
|
||||||
|
rf_ws_ids() {
|
||||||
|
local f
|
||||||
|
RF_WS_IDS=()
|
||||||
|
[[ -d $RF_WSDIR ]] || return 0
|
||||||
|
for f in "$RF_WSDIR"/*; do
|
||||||
|
[[ -f $f ]] || continue
|
||||||
|
RF_WS_IDS+=("${f##*/}")
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# rf_ws_load <id>
|
||||||
|
# Sets WS_URL, WS_NAME, WS_USER, WS_DOMAIN, WS_REFRESHED and WS_ACCOUNT, the
|
||||||
|
# name the way Windows writes it (DOMAIN\user, or user@domain as typed).
|
||||||
|
rf_ws_load() {
|
||||||
|
local file
|
||||||
|
file="$(rf_ws_file "$1")"
|
||||||
|
_rf_kv_lookup "$file" Url; WS_URL="$RF_KV_VALUE"
|
||||||
|
_rf_kv_lookup "$file" Name "$1"; WS_NAME="$RF_KV_VALUE"
|
||||||
|
_rf_kv_lookup "$file" User; WS_USER="$RF_KV_VALUE"
|
||||||
|
_rf_kv_lookup "$file" Domain; WS_DOMAIN="$RF_KV_VALUE"
|
||||||
|
_rf_kv_lookup "$file" Refreshed 0; WS_REFRESHED="$RF_KV_VALUE"
|
||||||
|
if [[ -n $WS_DOMAIN && $WS_USER != *@* ]]; then
|
||||||
|
WS_ACCOUNT="$WS_DOMAIN\\$WS_USER"
|
||||||
|
else
|
||||||
|
WS_ACCOUNT="$WS_USER"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
rf_ws_set() {
|
||||||
|
rf_kv_set "$(rf_ws_file "$1")" "$2" "$3" "$RF_PRETTY workspace"
|
||||||
|
}
|
||||||
@@ -0,0 +1,307 @@
|
|||||||
|
# shellcheck shell=bash
|
||||||
|
#
|
||||||
|
# Starting an app.
|
||||||
|
#
|
||||||
|
# Each app runs in xfreerdp in RemoteApp mode: only its own windows show, not
|
||||||
|
# a whole Windows desktop. The arguments, the password among them, go to
|
||||||
|
# FreeRDP on stdin (/args-from:stdin), never on its command line.
|
||||||
|
#
|
||||||
|
# A file from this computer (an "Open with", or `rdfeed run APP FILE`) gets
|
||||||
|
# there the way Windows does it: its folder is shared as a drive, and the app
|
||||||
|
# is started with the path on that drive, \\tsclient\<folder>\<file>.
|
||||||
|
#
|
||||||
|
# rdfeed-hook.so goes into FreeRDP with LD_PRELOAD. It waits before the second
|
||||||
|
# gateway sign-in so a second MFA prompt is sent, and keeps the graphics
|
||||||
|
# pipeline off, which leaves windows unpainted in RemoteApp mode. See
|
||||||
|
# src/hook/rdfeed-hook.c.
|
||||||
|
|
||||||
|
# How long the hook waits before the second sign-in, in seconds.
|
||||||
|
RF_GW_DELAY=12
|
||||||
|
|
||||||
|
# rf_freerdp
|
||||||
|
# The FreeRDP 3 client that can show RemoteApps (the X11 one), in RF_FREERDP.
|
||||||
|
# Arch and Debian call it xfreerdp3, Fedora xfreerdp.
|
||||||
|
RF_FREERDP=''
|
||||||
|
|
||||||
|
rf_freerdp() {
|
||||||
|
RF_FREERDP=''
|
||||||
|
if rf_have xfreerdp3; then
|
||||||
|
RF_FREERDP=xfreerdp3
|
||||||
|
elif rf_have xfreerdp && [[ $(xfreerdp /version 2>/dev/null) == *" 3."* ]]; then
|
||||||
|
RF_FREERDP=xfreerdp
|
||||||
|
fi
|
||||||
|
[[ -n $RF_FREERDP ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# rf_kbd_layout
|
||||||
|
# The Windows id of the keyboard layout in use. Under Wayland, X11 programs
|
||||||
|
# often see "us" whatever the real layout is, so it is asked of the desktop.
|
||||||
|
rf_kbd_layout() {
|
||||||
|
local layout=''
|
||||||
|
if [[ -r $RF_XDG_CONFIG/kxkbrc ]]; then
|
||||||
|
layout="$(sed -n 's/^LayoutList=//p' "$RF_XDG_CONFIG/kxkbrc" | head -n1)"
|
||||||
|
fi
|
||||||
|
if [[ -z $layout && ${XDG_CURRENT_DESKTOP:-} == *GNOME* ]] && rf_have gsettings; then
|
||||||
|
layout="$(gsettings get org.gnome.desktop.input-sources sources 2>/dev/null \
|
||||||
|
| sed -n "s/^[^']*'xkb', '\([a-z]*\).*/\1/p")"
|
||||||
|
fi
|
||||||
|
if [[ -z $layout ]] && rf_have localectl; then
|
||||||
|
layout="$(localectl status 2>/dev/null | sed -n 's/.*X11 Layout: *//p')"
|
||||||
|
fi
|
||||||
|
[[ -z $layout ]] && layout="${XKB_DEFAULT_LAYOUT:-}"
|
||||||
|
layout="${layout%%,*}"
|
||||||
|
case "${layout,,}" in
|
||||||
|
de|at) printf '0x00000407\n' ;;
|
||||||
|
ch) printf '0x00000807\n' ;;
|
||||||
|
us) printf '0x00000409\n' ;;
|
||||||
|
gb|uk) printf '0x00000809\n' ;;
|
||||||
|
fr) printf '0x0000040C\n' ;;
|
||||||
|
be) printf '0x0000080C\n' ;;
|
||||||
|
es) printf '0x0000040A\n' ;;
|
||||||
|
it) printf '0x00000410\n' ;;
|
||||||
|
nl) printf '0x00000413\n' ;;
|
||||||
|
pl) printf '0x00000415\n' ;;
|
||||||
|
pt) printf '0x00000816\n' ;;
|
||||||
|
br) printf '0x00000416\n' ;;
|
||||||
|
ru) printf '0x00000419\n' ;;
|
||||||
|
ua) printf '0x00000422\n' ;;
|
||||||
|
tr) printf '0x0000041F\n' ;;
|
||||||
|
jp) printf '0x00000411\n' ;;
|
||||||
|
kr) printf '0x00000412\n' ;;
|
||||||
|
se) printf '0x0000041D\n' ;;
|
||||||
|
no) printf '0x00000414\n' ;;
|
||||||
|
dk) printf '0x00000406\n' ;;
|
||||||
|
fi) printf '0x0000040B\n' ;;
|
||||||
|
cz) printf '0x00000405\n' ;;
|
||||||
|
sk) printf '0x0000041B\n' ;;
|
||||||
|
hu) printf '0x0000040E\n' ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# rf_auto_scale
|
||||||
|
# How much the desktop scales X11 programs that scale themselves, in percent,
|
||||||
|
# in RF_AUTO_SCALE. The apps are then drawn that much bigger on the server, so
|
||||||
|
# they come out sharp and the right size.
|
||||||
|
RF_AUTO_SCALE=100
|
||||||
|
|
||||||
|
rf_auto_scale() {
|
||||||
|
local s dpi
|
||||||
|
RF_AUTO_SCALE=100
|
||||||
|
s="$(awk -F= '/^\[/ { section = $0 } section == "[Xwayland]" && $1 == "Scale" { print $2 }' \
|
||||||
|
"$RF_XDG_CONFIG/kwinrc" 2>/dev/null | tail -n1)"
|
||||||
|
if [[ $s =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
|
||||||
|
RF_AUTO_SCALE="$(awk -v s="$s" 'BEGIN { printf "%d", s * 100 + 0.5 }')"
|
||||||
|
elif rf_have xrdb; then
|
||||||
|
dpi="$(xrdb -query 2>/dev/null | awk '/^Xft\.dpi:/ { print $2 }')"
|
||||||
|
[[ $dpi =~ ^[0-9]+$ ]] && RF_AUTO_SCALE=$(( (dpi * 100 + 48) / 96 ))
|
||||||
|
fi
|
||||||
|
[[ $RF_AUTO_SCALE -lt 100 ]] && RF_AUTO_SCALE=100
|
||||||
|
[[ $RF_AUTO_SCALE -gt 500 ]] && RF_AUTO_SCALE=500
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# rf_share_dir
|
||||||
|
# The folder the apps may see, from the Share setting. Nothing for off.
|
||||||
|
rf_share_dir() {
|
||||||
|
local dir=''
|
||||||
|
case "$CFG_SHARE" in
|
||||||
|
home) dir="$HOME" ;;
|
||||||
|
documents) rf_have xdg-user-dir && dir="$(xdg-user-dir DOCUMENTS)" ;;
|
||||||
|
downloads) rf_have xdg-user-dir && dir="$(xdg-user-dir DOWNLOAD)" ;;
|
||||||
|
esac
|
||||||
|
# xdg-user-dir answers with the home folder for a folder that is not set
|
||||||
|
# up. Sharing all of it then would be a surprise.
|
||||||
|
[[ $CFG_SHARE != home && ${dir%/} == "${HOME%/}" ]] && return 1
|
||||||
|
[[ -n $dir && -d $dir ]] || return 1
|
||||||
|
printf '%s\n' "$dir"
|
||||||
|
}
|
||||||
|
|
||||||
|
# _rf_drive_name <dir>
|
||||||
|
# A name for a shared folder, as the app shows it: the folder's own name,
|
||||||
|
# without what Windows does not allow in one.
|
||||||
|
_rf_drive_name() {
|
||||||
|
local name="${1%/}"
|
||||||
|
name="${name##*/}"
|
||||||
|
name="${name//[\\\/:*?\"<>|,;]/_}"
|
||||||
|
printf '%s\n' "${name:-Linux}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# _rf_drive_path <dir>
|
||||||
|
# The folder as FreeRDP can take it. /drive splits at commas, so a folder with
|
||||||
|
# one in its path is shared through a link that has none.
|
||||||
|
_rf_drive_path() {
|
||||||
|
local dir="$1" link
|
||||||
|
if [[ $dir != *,* ]]; then
|
||||||
|
printf '%s\n' "$dir"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
link="$RF_RUNDIR/links/$(_rf_drive_name "$dir")"
|
||||||
|
link="${link//,/_}"
|
||||||
|
mkdir -p "${link%/*}" && ln -sfn "$dir" "$link" && printf '%s\n' "$link"
|
||||||
|
}
|
||||||
|
|
||||||
|
# rf_ask_password <account>
|
||||||
|
# For when the keyring has none: in the terminal if there is one, else in a
|
||||||
|
# small window.
|
||||||
|
rf_ask_password() {
|
||||||
|
local prompt password
|
||||||
|
prompt="$(rf_msg "Password for %s:" "$1")"
|
||||||
|
if [[ -t 0 ]]; then
|
||||||
|
IFS= read -rsp " $prompt " password || return 1
|
||||||
|
printf '\n' >&2
|
||||||
|
elif rf_have kdialog; then
|
||||||
|
password="$(kdialog --title "$RF_PRETTY" --password "$prompt" 2>/dev/null)" || return 1
|
||||||
|
elif rf_have zenity; then
|
||||||
|
password="$(zenity --password --title "$prompt" 2>/dev/null)" || return 1
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
[[ -n $password ]] || return 1
|
||||||
|
printf '%s' "$password"
|
||||||
|
}
|
||||||
|
|
||||||
|
# rf_launch_error <log>
|
||||||
|
# Why FreeRDP gave up, in words, from what it logged.
|
||||||
|
rf_launch_error() {
|
||||||
|
local log="$1" last
|
||||||
|
if grep -qE 'ERRCONNECT_(PASSWORD_EXPIRED|PASSWORD_MUST_CHANGE|PASSWORD_CERTAINLY_EXPIRED)' "$log"; then
|
||||||
|
rf_msg "Your password has expired. Change it on Windows or on the RD Web page, then sign in again in rdfeed."
|
||||||
|
elif grep -qE 'ERRCONNECT_ACCOUNT_LOCKED_OUT' "$log"; then
|
||||||
|
rf_msg "Your account is locked. Ask your IT department."
|
||||||
|
elif grep -qE 'ERRCONNECT_(LOGON_FAILURE|WRONG_PASSWORD|AUTHENTICATION_FAILED)|STATUS_LOGON_FAILURE' "$log"; then
|
||||||
|
rf_msg "The server did not accept the password. Sign in again in rdfeed."
|
||||||
|
elif grep -qE 'ERRCONNECT_(DNS_NAME_NOT_FOUND|DNS_ERROR|CONNECT_FAILED|CONNECT_TRANSPORT_FAILED)' "$log"; then
|
||||||
|
rf_msg "Could not reach the server. Check the internet connection."
|
||||||
|
else
|
||||||
|
last="$(grep '\[ERROR\]' "$log" | tail -n1 | sed 's/^.*\] - \[[^]]*\]: *//')"
|
||||||
|
rf_msg "FreeRDP stopped: %s" "${last:-?}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# rf_launch <id> <app> [file]
|
||||||
|
rf_launch() {
|
||||||
|
local id="$1" slug="$2" file="${3:-}" i title='' type='' icon rdp password log rcfile rc line kbd share
|
||||||
|
local drive='' remote='' temprdp='' dir name rel
|
||||||
|
local -a args env
|
||||||
|
|
||||||
|
rf_ws_load "$id"
|
||||||
|
rf_ws_apps_load "$id"
|
||||||
|
for i in "${!RF_APP_SLUGS[@]}"; do
|
||||||
|
if [[ ${RF_APP_SLUGS[i]} == "$slug" ]]; then
|
||||||
|
title="${RF_APP_TITLES[i]}" type="${RF_APP_TYPES[i]}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
rdp="$RF_DATADIR/$id/rdp/$slug.rdp"
|
||||||
|
icon="$RF_DATADIR/$id/icons/$slug.png"
|
||||||
|
[[ -f $icon ]] || icon=rdfeed
|
||||||
|
if [[ -z $title || ! -f $rdp ]]; then
|
||||||
|
rf_bad "$(rf_msg "There is no app %s. See \`%s list\`." "$id/$slug" "$RF_NAME")"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n $file ]]; then
|
||||||
|
file="$(realpath -e -- "$file" 2>/dev/null)" && [[ -f $file ]] || {
|
||||||
|
rf_bad "$(rf_msg "File not found: %s" "${3:-}")"
|
||||||
|
rf_notify "$title" "$(rf_msg "File not found: %s" "${3:-}")" dialog-error
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! rf_freerdp; then
|
||||||
|
rf_bad "$(rf_msg "FreeRDP 3 is missing. Install it to start the apps (the package is called freerdp).")"
|
||||||
|
rf_notify "$title" "$(rf_msg "FreeRDP 3 is missing. Install it to start the apps (the package is called freerdp).")" dialog-error
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
password="$(rf_secret_get "$id" "$WS_ACCOUNT")" || password=''
|
||||||
|
if [[ -z $password ]]; then
|
||||||
|
password="$(rf_ask_password "$WS_ACCOUNT")" || {
|
||||||
|
rf_notify "$title" "$(rf_msg "No password saved for %s. Sign in again in rdfeed." "$WS_NAME")" dialog-error
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
rf_config_load
|
||||||
|
args=("$rdp" "/u:$WS_USER")
|
||||||
|
[[ -n $WS_DOMAIN && $WS_USER != *@* ]] && args+=("/d:$WS_DOMAIN")
|
||||||
|
args+=("/p:$password" /cert:tofu +clipboard)
|
||||||
|
kbd="$(rf_kbd_layout)" && args+=("/kbd:layout:$kbd")
|
||||||
|
[[ $CFG_SOUND == yes ]] && args+=(/sound)
|
||||||
|
if [[ $CFG_SCALE == auto ]]; then
|
||||||
|
rf_auto_scale
|
||||||
|
else
|
||||||
|
RF_AUTO_SCALE="$CFG_SCALE"
|
||||||
|
fi
|
||||||
|
[[ $RF_AUTO_SCALE -gt 100 ]] && args+=("/scale-desktop:$RF_AUTO_SCALE")
|
||||||
|
if share="$(rf_share_dir)"; then
|
||||||
|
name="$(_rf_drive_name "$share")"
|
||||||
|
args+=("/drive:$name,$(_rf_drive_path "$share")")
|
||||||
|
# A file in the shared folder needs no drive of its own.
|
||||||
|
if [[ -n $file && $file == "${share%/}"/* ]]; then
|
||||||
|
drive="$name"
|
||||||
|
rel="${file#"${share%/}"/}"
|
||||||
|
remote="\\\\tsclient\\$name\\${rel//\//\\}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [[ -n $file && -z $remote ]]; then
|
||||||
|
dir="${file%/*}"
|
||||||
|
drive="$(_rf_drive_name "$dir")"
|
||||||
|
args+=("/drive:$drive,$(_rf_drive_path "${dir:-/}")")
|
||||||
|
remote="\\\\tsclient\\$drive\\${file##*/}"
|
||||||
|
fi
|
||||||
|
if [[ -n $remote ]]; then
|
||||||
|
# The app gets the file as its command line, in a copy of the .rdp.
|
||||||
|
mkdir -p "$RF_RUNDIR"
|
||||||
|
temprdp="$(mktemp "$RF_RUNDIR/$id--$slug.XXXXXX.rdp")"
|
||||||
|
RF_CMDLINE="\"$remote\"" awk '
|
||||||
|
BEGIN { line = "remoteapplicationcmdline:s:" ENVIRON["RF_CMDLINE"] }
|
||||||
|
tolower($0) ~ /^remoteapplicationcmdline:s:/ { if (!done) print line "\r"; done = 1; next }
|
||||||
|
{ print }
|
||||||
|
END { if (!done) print line "\r" }
|
||||||
|
' "$rdp" > "$temprdp"
|
||||||
|
args[0]="$temprdp"
|
||||||
|
fi
|
||||||
|
[[ $type == Desktop ]] && args+=(/dynamic-resolution)
|
||||||
|
|
||||||
|
env=(WLOG_LEVEL="${WLOG_LEVEL:-INFO}" RDFEED_GW_WAIT="$CFG_MFAWAIT" RDFEED_GW_DELAY="$RF_GW_DELAY")
|
||||||
|
[[ $CFG_GRAPHICS == gfx ]] || env+=(RDFEED_NO_GFX=1)
|
||||||
|
[[ -f $RF_HOOK ]] && env+=(LD_PRELOAD="$RF_HOOK${LD_PRELOAD:+:$LD_PRELOAD}")
|
||||||
|
|
||||||
|
mkdir -p "$RF_CACHEDIR"
|
||||||
|
log="$RF_CACHEDIR/$id--$slug.log"
|
||||||
|
rcfile="$(mktemp "$RF_CACHEDIR/.exit.XXXXXX")"
|
||||||
|
: > "$log"
|
||||||
|
|
||||||
|
if [[ -n $file ]]; then
|
||||||
|
rf_notify "$(rf_msg "Opening %s in %s" "${file##*/}" "$title")" \
|
||||||
|
"$(rf_msg "If it does not open by itself, open it in the app from the drive %s." "$drive")" "$icon"
|
||||||
|
else
|
||||||
|
rf_notify "$(rf_msg "Starting %s" "$title")" \
|
||||||
|
"$(rf_msg "If your company uses MFA, confirm the sign-in in your authenticator app.")" "$icon"
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
printf '%s\n' "${args[@]}" | env "${env[@]}" "$RF_FREERDP" /args-from:stdin 2>&1
|
||||||
|
printf '%s\n' "${PIPESTATUS[1]}" > "$rcfile"
|
||||||
|
} | while IFS= read -r line; do
|
||||||
|
printf '%s\n' "$line" >> "$log"
|
||||||
|
if [[ $line == "[rdfeed] gateway-delay"* ]]; then
|
||||||
|
rf_notify "$(rf_msg "A second sign-in is coming")" \
|
||||||
|
"$(rf_msg "Confirm the second prompt in your authenticator app too. It comes in about %d seconds." "$RF_GW_DELAY")" "$icon"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
rc="$(< "$rcfile")"
|
||||||
|
rm -f "$rcfile"
|
||||||
|
[[ -n $temprdp ]] && rm -f "$temprdp"
|
||||||
|
|
||||||
|
# Below 128 the session ended the normal way: closed, logged off, or taken
|
||||||
|
# over by the next app of the same workspace.
|
||||||
|
if [[ $rc =~ ^[0-9]+$ ]] && (( rc >= 128 )) && ! grep -q ERRCONNECT_CONNECT_CANCELLED "$log"; then
|
||||||
|
rf_notify "$(rf_msg "%s could not start" "$title")" "$(rf_launch_error "$log")" dialog-error
|
||||||
|
rf_bad "$(rf_launch_error "$log")"
|
||||||
|
rf_note "$(rf_msg "Log: %s" "$log")"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
+635
@@ -0,0 +1,635 @@
|
|||||||
|
# shellcheck shell=bash
|
||||||
|
#
|
||||||
|
# The interactive front end.
|
||||||
|
#
|
||||||
|
# This is the whole configuration interface. There are files behind it, but no
|
||||||
|
# part of the program ever asks anybody to open one: the workspaces, the apps,
|
||||||
|
# a settings list.
|
||||||
|
|
||||||
|
# Terminal mode.
|
||||||
|
#
|
||||||
|
# bash flips the terminal into non-canonical mode for each `read -sn1` and back
|
||||||
|
# out again in between. That gap matters: in canonical mode DEL is the ERASE
|
||||||
|
# character, so the line discipline eats it instead of delivering it, and a
|
||||||
|
# backspace typed while the interface was between reads simply vanishes.
|
||||||
|
# Holding non-canonical mode for the whole interface removes the gap.
|
||||||
|
RF_TERM_SAVED=''
|
||||||
|
|
||||||
|
rf_ui_term_raw() {
|
||||||
|
rf_have stty || return 0
|
||||||
|
[[ -t 0 ]] || return 0
|
||||||
|
[[ -n $RF_TERM_SAVED ]] && return 0
|
||||||
|
|
||||||
|
RF_TERM_SAVED="$(stty -g 2>/dev/null)" || { RF_TERM_SAVED=''; return 0; }
|
||||||
|
stty -icanon -echo min 1 time 0 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
rf_ui_term_restore() {
|
||||||
|
[[ -n $RF_TERM_SAVED ]] || return 0
|
||||||
|
stty "$RF_TERM_SAVED" 2>/dev/null || true
|
||||||
|
RF_TERM_SAVED=''
|
||||||
|
}
|
||||||
|
|
||||||
|
# Runs an action with the terminal handed back to normal line mode, so what it
|
||||||
|
# prompts for (a user name, a password) behaves the way a program expects.
|
||||||
|
rf_ui_cooked() {
|
||||||
|
rf_ui_term_restore
|
||||||
|
"$@"
|
||||||
|
local rc=$?
|
||||||
|
rf_ui_term_raw
|
||||||
|
return $rc
|
||||||
|
}
|
||||||
|
|
||||||
|
# rf_read_key
|
||||||
|
# One keypress, resolved to a symbolic name. Arrow keys arrive as ESC [ A, so
|
||||||
|
# the tail of the sequence is consumed here rather than being mistaken for
|
||||||
|
# three separate presses.
|
||||||
|
rf_read_key() {
|
||||||
|
local k rest
|
||||||
|
|
||||||
|
IFS= read -rsn1 k || return 1
|
||||||
|
|
||||||
|
case "$k" in
|
||||||
|
$'\e')
|
||||||
|
if IFS= read -rsn2 -t 0.05 rest; then
|
||||||
|
case "$rest" in
|
||||||
|
'[A') printf 'up\n' ;;
|
||||||
|
'[B') printf 'down\n' ;;
|
||||||
|
'[C') printf 'right\n' ;;
|
||||||
|
'[D') printf 'left\n' ;;
|
||||||
|
*) printf 'escape\n' ;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
printf 'escape\n'
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
''|$'\r') printf 'enter\n' ;;
|
||||||
|
$'\x7f'|$'\b') printf 'backspace\n' ;;
|
||||||
|
' ') printf 'space\n' ;;
|
||||||
|
*) printf '%s\n' "$k" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# _rf_ui_take_notices
|
||||||
|
# The messages the last action left, as lines for under a frame, in
|
||||||
|
# RF_UI_NOTICE_TEXT. Each is shown once.
|
||||||
|
RF_UI_NOTICE_TEXT=''
|
||||||
|
|
||||||
|
_rf_ui_take_notices() {
|
||||||
|
local n
|
||||||
|
RF_UI_NOTICE_TEXT=''
|
||||||
|
(( ${#RF_UI_NOTICES[@]} )) || return 0
|
||||||
|
RF_UI_NOTICE_TEXT=$'\n'
|
||||||
|
for n in "${RF_UI_NOTICES[@]}"; do
|
||||||
|
RF_UI_NOTICE_TEXT+=" $n"$'\n'
|
||||||
|
done
|
||||||
|
RF_UI_NOTICES=()
|
||||||
|
}
|
||||||
|
|
||||||
|
# rf_ui_confirm <question>
|
||||||
|
rf_ui_confirm() {
|
||||||
|
local key hint yes
|
||||||
|
# TRANSLATORS: the letter after "[" is the key for yes. y works too.
|
||||||
|
hint="$(rf_msg "[y/N]")"
|
||||||
|
yes="${hint:1:1}"
|
||||||
|
printf '\n %s %s ' "$1" "$hint"
|
||||||
|
key="$(rf_read_key)" || return 1
|
||||||
|
printf '%s\n' "$key"
|
||||||
|
[[ ${key,,} == y || ${key,,} == "${yes,,}" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# _rf_width <text>
|
||||||
|
# The columns the text takes, into RF_WIDTH. ${#s} counts characters, but
|
||||||
|
# Chinese, Japanese and Korean ones take two columns each.
|
||||||
|
RF_WIDTH=0
|
||||||
|
|
||||||
|
_rf_width() {
|
||||||
|
local wide="${1//[^ -〿-ヿ㐀-䶿一-鿿가-豈--⦆]/}"
|
||||||
|
RF_WIDTH=$(( ${#1} + ${#wide} ))
|
||||||
|
}
|
||||||
|
|
||||||
|
# _rf_pad <text> <columns>
|
||||||
|
# The text, padded with spaces to the columns, into RF_PADDED. printf's %-28s
|
||||||
|
# pads by bytes, so a label containing "ü" would come out one column short.
|
||||||
|
RF_PADDED=''
|
||||||
|
|
||||||
|
_rf_pad() {
|
||||||
|
local pad
|
||||||
|
_rf_width "$1"
|
||||||
|
pad=$(( $2 - RF_WIDTH ))
|
||||||
|
(( pad < 1 )) && pad=1
|
||||||
|
printf -v RF_PADDED '%s%*s' "$1" "$pad" ''
|
||||||
|
}
|
||||||
|
|
||||||
|
# _rf_wrap <width> <text>
|
||||||
|
# Word wrap, into RF_WRAP_LINES.
|
||||||
|
RF_WRAP_LINES=()
|
||||||
|
|
||||||
|
_rf_wrap() {
|
||||||
|
local width="$1" word line='' used=0 c i
|
||||||
|
local -a words
|
||||||
|
RF_WRAP_LINES=()
|
||||||
|
read -r -a words <<< "$2"
|
||||||
|
for word in "${words[@]}"; do
|
||||||
|
_rf_width "$word"
|
||||||
|
if (( used > 0 && used + 1 + RF_WIDTH > width )); then
|
||||||
|
RF_WRAP_LINES+=("$line")
|
||||||
|
line='' used=0
|
||||||
|
fi
|
||||||
|
(( used > 0 )) && line+=' ' used=$(( used + 1 ))
|
||||||
|
if (( used + RF_WIDTH <= width )); then
|
||||||
|
line+="$word" used=$(( used + RF_WIDTH ))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
# Longer than a line: Chinese and Japanese have no spaces, so break
|
||||||
|
# between any two characters.
|
||||||
|
for (( i = 0; i < ${#word}; i++ )); do
|
||||||
|
c="${word:i:1}"
|
||||||
|
_rf_width "$c"
|
||||||
|
if (( used + RF_WIDTH > width )); then
|
||||||
|
RF_WRAP_LINES+=("$line")
|
||||||
|
line='' used=0
|
||||||
|
fi
|
||||||
|
line+="$c" used=$(( used + RF_WIDTH ))
|
||||||
|
done
|
||||||
|
done
|
||||||
|
[[ -n $line ]] && RF_WRAP_LINES+=("$line")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# _rf_apps_label <count>
|
||||||
|
_rf_apps_label() {
|
||||||
|
if [[ $1 == 1 ]]; then
|
||||||
|
rf_msg "1 app"
|
||||||
|
else
|
||||||
|
rf_msg "%d apps" "$1"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# rf_start_detached <id> <app>
|
||||||
|
# Starts an app on its own, so it keeps running when the menu closes.
|
||||||
|
rf_start_detached() {
|
||||||
|
if rf_have setsid; then
|
||||||
|
setsid -f "$RF_SELF" run "$1/$2" > /dev/null 2>&1 < /dev/null
|
||||||
|
else
|
||||||
|
nohup "$RF_SELF" run "$1/$2" > /dev/null 2>&1 < /dev/null &
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Status
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# rf_ui_status
|
||||||
|
# The workspaces, for the head of the menu.
|
||||||
|
rf_ui_status() {
|
||||||
|
local id width=0 apps
|
||||||
|
|
||||||
|
rf_ws_ids
|
||||||
|
if (( ${#RF_WS_IDS[@]} == 0 )); then
|
||||||
|
printf ' %s\n' "$(rf_msg "No workspace yet. Add the one of your company with [1].")"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
for id in "${RF_WS_IDS[@]}"; do
|
||||||
|
rf_ws_load "$id"
|
||||||
|
_rf_width "$WS_NAME"
|
||||||
|
(( RF_WIDTH > width )) && width=$RF_WIDTH
|
||||||
|
done
|
||||||
|
for id in "${RF_WS_IDS[@]}"; do
|
||||||
|
rf_ws_load "$id"
|
||||||
|
rf_ws_apps_load "$id"
|
||||||
|
_rf_pad "$WS_NAME" $(( width + 3 ))
|
||||||
|
apps="$(_rf_apps_label "${#RF_APP_SLUGS[@]}")"
|
||||||
|
printf ' %s%-10s %s%s%s\n' "$RF_PADDED" "$apps" "$RF_C_DIM" "$WS_ACCOUNT" "$RF_C_RESET"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Settings
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Format: scope|Key|type|default|label-msgid|choices
|
||||||
|
# scope user (the settings file), entries (the settings file, and the app
|
||||||
|
# menu entries are written again), timer (the daily refresh, a
|
||||||
|
# systemd user timer), or group for a heading, with only the label
|
||||||
|
# after it
|
||||||
|
# type bool, or choice (steps through the choices)
|
||||||
|
RF_SETTINGS=(
|
||||||
|
"group|Apps"
|
||||||
|
"user|Share|choice|off|Share a folder|off,documents,downloads,home"
|
||||||
|
"entries|OpenWith|bool|yes|Open files with the apps"
|
||||||
|
"user|Scale|choice|auto|Size|auto,100,125,150,175,200"
|
||||||
|
"user|Sound|bool|yes|Sound"
|
||||||
|
"user|Graphics|choice|gfx|Graphics|gfx,classic"
|
||||||
|
"group|Signing in"
|
||||||
|
"user|MfaWait|choice|auto|Wait for the second MFA prompt|auto,always,never"
|
||||||
|
"timer|AutoRefresh|bool|yes|Refresh the apps every day"
|
||||||
|
)
|
||||||
|
|
||||||
|
# rf_value_label <Key> <value>
|
||||||
|
# How a setting's value reads in the menu.
|
||||||
|
rf_value_label() {
|
||||||
|
local dir
|
||||||
|
case "$1:$2" in
|
||||||
|
Share:off) rf_msg "off" ;;
|
||||||
|
Share:home) rf_msg "home folder" ;;
|
||||||
|
Share:documents|Share:downloads)
|
||||||
|
case "$2" in
|
||||||
|
documents) rf_msg "Documents" ;;
|
||||||
|
*) rf_msg "Downloads" ;;
|
||||||
|
esac
|
||||||
|
if dir="$(CFG_SHARE="$2" rf_share_dir)"; then
|
||||||
|
printf ' %s(%s)%s' "$RF_C_DIM" "${dir/#"$HOME"/\~}" "$RF_C_RESET"
|
||||||
|
else
|
||||||
|
printf ' %s(%s)%s' "$RF_C_YELLOW" "$(rf_msg "not found")" "$RF_C_RESET"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
Scale:auto) rf_auto_scale; rf_msg "automatic (%d %%)" "$RF_AUTO_SCALE" ;;
|
||||||
|
Scale:*) printf '%s %%' "$2" ;;
|
||||||
|
Graphics:classic) rf_msg "classic" ;;
|
||||||
|
Graphics:*) rf_msg "GFX" ;;
|
||||||
|
MfaWait:always) rf_msg "always" ;;
|
||||||
|
MfaWait:never) rf_msg "never" ;;
|
||||||
|
MfaWait:*) rf_msg "automatic" ;;
|
||||||
|
*) printf '%s' "$2" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# rf_setting_help <Key> <value>
|
||||||
|
# What a setting does, shown under the list for the selected one.
|
||||||
|
rf_setting_help() {
|
||||||
|
case "$1:$2" in
|
||||||
|
Share:off) rf_msg "The apps only see the files on the server. Pick a folder to open and save files of this computer in them." ;;
|
||||||
|
Share:*) rf_msg "The apps can open and save files in this folder. It shows up as a drive in them." ;;
|
||||||
|
OpenWith:*) rf_msg "Offers the apps under \"Open with\" for their file types, for example Word for .docx. The folder of the file is shared with the app while it is open." ;;
|
||||||
|
Scale:*) rf_msg "How big the apps are drawn. Automatic follows the scale of your desktop, so they are sharp and the right size." ;;
|
||||||
|
Sound:*) rf_msg "Plays the sound of the apps here, for calls and videos." ;;
|
||||||
|
Graphics:classic) rf_msg "Paints every window right. But some servers end the session right after signing in, then go back to GFX." ;;
|
||||||
|
Graphics:*) rf_msg "Works with every server. FreeRDP may not repaint a window after it was minimized or resized." ;;
|
||||||
|
MfaWait:always) rf_msg "Always waits before the second sign-in. For when the second prompt does not come with automatic." ;;
|
||||||
|
MfaWait:never) rf_msg "Never waits. Right when your company has no MFA." ;;
|
||||||
|
MfaWait:*) rf_msg "Many servers with MFA need a second sign-in, and its prompt only comes after a short wait. Automatic waits when the first sign-in took a while." ;;
|
||||||
|
AutoRefresh:*)
|
||||||
|
if rf_timer_available; then
|
||||||
|
rf_msg "Loads the list of apps again once a day, so new apps show up by themselves."
|
||||||
|
else
|
||||||
|
rf_msg "Needs a systemd user session."
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# _rf_setting_value <scope> <Key> <default>
|
||||||
|
# The current value, in RF_SETTING_VALUE.
|
||||||
|
RF_SETTING_VALUE=''
|
||||||
|
|
||||||
|
_rf_setting_value() {
|
||||||
|
case "$1" in
|
||||||
|
user|entries) _rf_kv_lookup "$RF_CONFIG" "$2" "$3"; RF_SETTING_VALUE="$RF_KV_VALUE" ;;
|
||||||
|
timer) if rf_timer_enabled; then RF_SETTING_VALUE=yes; else RF_SETTING_VALUE=no; fi ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# _rf_step <current> <step> <choice>...
|
||||||
|
# The choice <step> (1 or -1) away from the current one, round at the ends.
|
||||||
|
_rf_step() {
|
||||||
|
local current="$1" step="$2" i
|
||||||
|
shift 2
|
||||||
|
local -a all=("$@")
|
||||||
|
for i in "${!all[@]}"; do
|
||||||
|
if [[ ${all[i]} == "$current" ]]; then
|
||||||
|
printf '%s\n' "${all[(i + step + ${#all[@]}) % ${#all[@]}]}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
printf '%s\n' "${all[0]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# _rf_setting_change <scope> <Key> <type> <current> <choices> <step>
|
||||||
|
_rf_setting_change() {
|
||||||
|
local scope="$1" key="$2" type="$3" current="$4" options="$5" step="$6" next
|
||||||
|
local -a choices
|
||||||
|
|
||||||
|
case "$type" in
|
||||||
|
bool) if rf_is_true "$current"; then next=no; else next=yes; fi ;;
|
||||||
|
choice)
|
||||||
|
IFS=',' read -r -a choices <<< "$options"
|
||||||
|
next="$(_rf_step "$current" "$step" "${choices[@]}")"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "$scope" in
|
||||||
|
user)
|
||||||
|
rf_config_set "$key" "$next" || rf_bad "$(rf_msg "Could not save the setting.")"
|
||||||
|
;;
|
||||||
|
entries)
|
||||||
|
rf_config_set "$key" "$next" || rf_bad "$(rf_msg "Could not save the setting.")"
|
||||||
|
rf_ws_entries_all
|
||||||
|
;;
|
||||||
|
timer)
|
||||||
|
rf_config_set "$key" "$next"
|
||||||
|
rf_timer_set "$next" || rf_bad "$(rf_msg "Needs a systemd user session.")"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# rf_ui_settings
|
||||||
|
# A cursor list in groups, with what the selected setting does under it. The
|
||||||
|
# frame is assembled in memory and written once, and everything constant is
|
||||||
|
# resolved before the loop.
|
||||||
|
rf_ui_settings() {
|
||||||
|
local -a scopes=() keys=() types=() defaults=() labels=() widths=() choices=() values=() rows=()
|
||||||
|
local spec scope key type default label choice locale i j frame row pad dirty=1 cursor=0 shown
|
||||||
|
local width=0 wrap cols
|
||||||
|
|
||||||
|
locale="$(rf_ui_locale)"
|
||||||
|
for spec in "${RF_SETTINGS[@]}"; do
|
||||||
|
IFS='|' read -r scope key type default label choice <<< "$spec"
|
||||||
|
# A heading has its label where the key would be.
|
||||||
|
[[ $scope == group ]] && label="$key" key=''
|
||||||
|
scopes+=("$scope"); keys+=("$key"); types+=("$type"); defaults+=("$default")
|
||||||
|
choices+=("$choice")
|
||||||
|
rf_msg_into "$locale" "$label"
|
||||||
|
labels+=("$RF_MSG_RESULT")
|
||||||
|
_rf_width "$RF_MSG_RESULT"
|
||||||
|
widths+=("$RF_WIDTH")
|
||||||
|
if [[ $scope != group ]]; then
|
||||||
|
rows+=($(( ${#keys[@]} - 1 )))
|
||||||
|
(( RF_WIDTH > width )) && width=$RF_WIDTH
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
local count=${#rows[@]}
|
||||||
|
|
||||||
|
cols="$(tput cols 2>/dev/null)" || cols=80
|
||||||
|
[[ $cols =~ ^[0-9]+$ ]] || cols=80
|
||||||
|
wrap=$(( cols - 4 ))
|
||||||
|
(( wrap > 72 )) && wrap=72
|
||||||
|
local rule
|
||||||
|
printf -v rule '%*s' "$wrap" ''
|
||||||
|
rule="${rule// /─}"
|
||||||
|
|
||||||
|
local title hint l_on l_off
|
||||||
|
rf_msg_into "$locale" "Settings"; title="$RF_MSG_RESULT"
|
||||||
|
rf_msg_into "$locale" "↑↓ select ←→ or Space: change q: back"; hint="$RF_MSG_RESULT"
|
||||||
|
rf_msg_into "$locale" "ON"; l_on="$RF_MSG_RESULT"
|
||||||
|
rf_msg_into "$locale" "OFF"; l_off="$RF_MSG_RESULT"
|
||||||
|
|
||||||
|
local clearseq
|
||||||
|
clearseq="$(clear 2>/dev/null)" || clearseq=$'\033[H\033[2J'
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
if (( dirty )); then
|
||||||
|
RF_KV_CACHE=()
|
||||||
|
for i in "${rows[@]}"; do
|
||||||
|
_rf_setting_value "${scopes[i]}" "${keys[i]}" "${defaults[i]}"
|
||||||
|
values[i]="$RF_SETTING_VALUE"
|
||||||
|
done
|
||||||
|
dirty=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
frame="$clearseq"$'\n'"${RF_C_BOLD}${RF_C_BLUE} ${title}${RF_C_RESET}"$'\n'
|
||||||
|
|
||||||
|
local selected=${rows[cursor]} marker before after
|
||||||
|
for i in "${!keys[@]}"; do
|
||||||
|
if [[ ${scopes[i]} == group ]]; then
|
||||||
|
frame+=$'\n'" ${RF_C_BOLD}${labels[i]}${RF_C_RESET}"$'\n'
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [[ ${types[i]} == bool ]]; then
|
||||||
|
if rf_is_true "${values[i]}"; then
|
||||||
|
shown="${RF_C_GREEN}${l_on}${RF_C_RESET}"
|
||||||
|
else
|
||||||
|
shown="${RF_C_DIM}${l_off}${RF_C_RESET}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
shown="$(rf_value_label "${keys[i]}" "${values[i]}")"
|
||||||
|
fi
|
||||||
|
# Arrows on the selected choice: left and right step through it.
|
||||||
|
before=' ' after=''
|
||||||
|
if (( i == selected )) && [[ ${types[i]} != bool ]]; then
|
||||||
|
before="${RF_C_BLUE}◂${RF_C_RESET} " after=" ${RF_C_BLUE}▸${RF_C_RESET}"
|
||||||
|
fi
|
||||||
|
pad=$(( width + 2 - widths[i] ))
|
||||||
|
if (( i == selected )); then marker="${RF_C_BLUE}▸${RF_C_RESET} "; else marker=' '; fi
|
||||||
|
printf -v row ' %s%s%*s%s%s%s' "$marker" "${labels[i]}" "$pad" '' "$before" "$shown$RF_C_RESET" "$after"
|
||||||
|
frame+="$row"$'\n'
|
||||||
|
done
|
||||||
|
|
||||||
|
# What the selected setting does, in a box of fixed height so the
|
||||||
|
# screen does not jump while moving through the list.
|
||||||
|
_rf_wrap "$wrap" "$(rf_setting_help "${keys[selected]}" "${values[selected]}")"
|
||||||
|
frame+=$'\n'" ${RF_C_DIM}${rule}${RF_C_RESET}"$'\n'
|
||||||
|
for j in 0 1 2; do
|
||||||
|
frame+=" ${RF_WRAP_LINES[j]:-}"$'\n'
|
||||||
|
done
|
||||||
|
frame+=$'\n'" ${RF_C_DIM}${hint}${RF_C_RESET}"$'\n'
|
||||||
|
_rf_ui_take_notices
|
||||||
|
frame+="$RF_UI_NOTICE_TEXT"
|
||||||
|
printf '%s' "$frame"
|
||||||
|
|
||||||
|
key="$(rf_read_key)" || return 0
|
||||||
|
|
||||||
|
case "$key" in
|
||||||
|
up|k) cursor=$(( (cursor - 1 + count) % count )) ;;
|
||||||
|
down|j) cursor=$(( (cursor + 1) % count )) ;;
|
||||||
|
space|right|l|left|h)
|
||||||
|
local step=1
|
||||||
|
[[ $key == left || $key == h ]] && step=-1
|
||||||
|
_rf_setting_change "${scopes[selected]}" "${keys[selected]}" "${types[selected]}" "${values[selected]}" "${choices[selected]}" "$step"
|
||||||
|
dirty=1
|
||||||
|
;;
|
||||||
|
q|Q|escape) return 0 ;;
|
||||||
|
*) ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Workspaces
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
rf_ui_workspaces() {
|
||||||
|
local key frame row i cursor=0 count locale width
|
||||||
|
local -a ids=()
|
||||||
|
|
||||||
|
locale="$(rf_ui_locale)"
|
||||||
|
local title hint empty
|
||||||
|
rf_msg_into "$locale" "Workspaces"; title="$RF_MSG_RESULT"
|
||||||
|
# TRANSLATORS: keep the letters, they are the keys.
|
||||||
|
rf_msg_into "$locale" "↑↓ select r: refresh s: sign in again d: remove a: add q: back"; hint="$RF_MSG_RESULT"
|
||||||
|
rf_msg_into "$locale" "No workspace yet. Press a to add one."; empty="$RF_MSG_RESULT"
|
||||||
|
|
||||||
|
local clearseq
|
||||||
|
clearseq="$(clear 2>/dev/null)" || clearseq=$'\033[H\033[2J'
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
rf_ws_ids
|
||||||
|
ids=("${RF_WS_IDS[@]}")
|
||||||
|
count=${#ids[@]}
|
||||||
|
(( cursor >= count )) && cursor=$(( count > 0 ? count - 1 : 0 ))
|
||||||
|
|
||||||
|
width=0
|
||||||
|
for i in "${!ids[@]}"; do
|
||||||
|
rf_ws_load "${ids[i]}"
|
||||||
|
_rf_width "$WS_NAME"
|
||||||
|
(( RF_WIDTH > width )) && width=$RF_WIDTH
|
||||||
|
done
|
||||||
|
|
||||||
|
frame="$clearseq"$'\n'"${RF_C_BOLD}${RF_C_BLUE} ${title}${RF_C_RESET}"$'\n\n'
|
||||||
|
(( count == 0 )) && frame+=" ${RF_C_DIM}${empty}${RF_C_RESET}"$'\n'
|
||||||
|
local marker apps when
|
||||||
|
for i in "${!ids[@]}"; do
|
||||||
|
rf_ws_load "${ids[i]}"
|
||||||
|
rf_ws_apps_load "${ids[i]}"
|
||||||
|
apps="$(_rf_apps_label "${#RF_APP_SLUGS[@]}")"
|
||||||
|
when="$(rf_msg "refreshed %s" "$(rf_time_ago "$WS_REFRESHED")")"
|
||||||
|
_rf_pad "$WS_NAME" $(( width + 3 ))
|
||||||
|
if (( i == cursor )); then marker="${RF_C_BLUE}▸${RF_C_RESET} "; else marker=' '; fi
|
||||||
|
printf -v row ' %s%s%-10s %s %s%s%s' "$marker" "$RF_PADDED" "$apps" "$WS_ACCOUNT" "$RF_C_DIM" "$when" "$RF_C_RESET"
|
||||||
|
frame+="$row"$'\n'
|
||||||
|
done
|
||||||
|
frame+=$'\n'" ${RF_C_DIM}${hint}${RF_C_RESET}"$'\n'
|
||||||
|
_rf_ui_take_notices
|
||||||
|
frame+="$RF_UI_NOTICE_TEXT"
|
||||||
|
printf '%s' "$frame"
|
||||||
|
|
||||||
|
key="$(rf_read_key)" || return 0
|
||||||
|
case "$key" in
|
||||||
|
up|k) (( count )) && cursor=$(( (cursor - 1 + count) % count )) ;;
|
||||||
|
down|j) (( count )) && cursor=$(( (cursor + 1) % count )) ;;
|
||||||
|
r|R)
|
||||||
|
(( count )) || continue
|
||||||
|
printf '\n'
|
||||||
|
rf_ui_cooked rf_do_refresh "${ids[cursor]}"
|
||||||
|
;;
|
||||||
|
s|S)
|
||||||
|
(( count )) || continue
|
||||||
|
printf '\n'
|
||||||
|
rf_ui_cooked rf_do_signin_again "${ids[cursor]}"
|
||||||
|
;;
|
||||||
|
d|D)
|
||||||
|
(( count )) || continue
|
||||||
|
rf_ws_load "${ids[cursor]}"
|
||||||
|
if rf_ui_confirm "$(rf_msg "Remove \"%s\" and its apps?" "$WS_NAME")"; then
|
||||||
|
rf_do_remove "${ids[cursor]}" > /dev/null
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
a|A)
|
||||||
|
printf '\n'
|
||||||
|
rf_ui_cooked rf_do_add
|
||||||
|
;;
|
||||||
|
q|Q|escape) return 0 ;;
|
||||||
|
*) ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Apps
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
rf_ui_apps() {
|
||||||
|
local key frame row i id cursor=0 count locale
|
||||||
|
local -a ws=() slugs=() titles=()
|
||||||
|
|
||||||
|
locale="$(rf_ui_locale)"
|
||||||
|
local title hint empty
|
||||||
|
rf_msg_into "$locale" "Start an app"; title="$RF_MSG_RESULT"
|
||||||
|
rf_msg_into "$locale" "↑↓ select Enter: start q: back"; hint="$RF_MSG_RESULT"
|
||||||
|
rf_msg_into "$locale" "No apps yet. Add a workspace first."; empty="$RF_MSG_RESULT"
|
||||||
|
|
||||||
|
rf_ws_ids
|
||||||
|
for id in "${RF_WS_IDS[@]}"; do
|
||||||
|
rf_ws_apps_load "$id"
|
||||||
|
for i in "${!RF_APP_SLUGS[@]}"; do
|
||||||
|
ws+=("$id") slugs+=("${RF_APP_SLUGS[i]}") titles+=("${RF_APP_TITLES[i]}")
|
||||||
|
done
|
||||||
|
done
|
||||||
|
count=${#slugs[@]}
|
||||||
|
|
||||||
|
local clearseq
|
||||||
|
clearseq="$(clear 2>/dev/null)" || clearseq=$'\033[H\033[2J'
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
frame="$clearseq"$'\n'"${RF_C_BOLD}${RF_C_BLUE} ${title}${RF_C_RESET}"$'\n'
|
||||||
|
(( count == 0 )) && frame+=$'\n'" ${RF_C_DIM}${empty}${RF_C_RESET}"$'\n'
|
||||||
|
local marker last=''
|
||||||
|
for i in "${!slugs[@]}"; do
|
||||||
|
# A heading for each workspace.
|
||||||
|
if [[ ${ws[i]} != "$last" ]]; then
|
||||||
|
rf_ws_load "${ws[i]}"
|
||||||
|
frame+=$'\n'" ${RF_C_BOLD}${WS_NAME}${RF_C_RESET}"$'\n'
|
||||||
|
last="${ws[i]}"
|
||||||
|
fi
|
||||||
|
if (( i == cursor )); then marker="${RF_C_BLUE}▸${RF_C_RESET} "; else marker=' '; fi
|
||||||
|
printf -v row ' %s%s' "$marker" "${titles[i]}"
|
||||||
|
frame+="$row"$'\n'
|
||||||
|
done
|
||||||
|
frame+=$'\n'" ${RF_C_DIM}${hint}${RF_C_RESET}"$'\n'
|
||||||
|
_rf_ui_take_notices
|
||||||
|
frame+="$RF_UI_NOTICE_TEXT"
|
||||||
|
printf '%s' "$frame"
|
||||||
|
|
||||||
|
key="$(rf_read_key)" || return 0
|
||||||
|
case "$key" in
|
||||||
|
up|k) (( count )) && cursor=$(( (cursor - 1 + count) % count )) ;;
|
||||||
|
down|j) (( count )) && cursor=$(( (cursor + 1) % count )) ;;
|
||||||
|
enter|space)
|
||||||
|
(( count )) || continue
|
||||||
|
rf_start_detached "${ws[cursor]}" "${slugs[cursor]}"
|
||||||
|
rf_ok "$(rf_msg "%s is starting. It opens in a window of its own." "${titles[cursor]}")" > /dev/null
|
||||||
|
;;
|
||||||
|
q|Q|escape) return 0 ;;
|
||||||
|
*) ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# The menu
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
rf_ui_menu() {
|
||||||
|
local choice
|
||||||
|
|
||||||
|
if [[ ! -t 0 ]]; then
|
||||||
|
rf_do_help
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
rf_ui_term_raw
|
||||||
|
trap 'rf_ui_term_restore' EXIT INT TERM
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
clear 2>/dev/null || true
|
||||||
|
rf_head " $RF_PRETTY"
|
||||||
|
rf_ui_status
|
||||||
|
printf '\n'
|
||||||
|
printf ' [1] %s\n' "$(rf_msg "Add a workspace")"
|
||||||
|
printf ' [2] %s\n' "$(rf_msg "Start an app")"
|
||||||
|
printf ' [3] %s\n' "$(rf_msg "Workspaces")"
|
||||||
|
printf ' [4] %s\n' "$(rf_msg "Settings")"
|
||||||
|
printf ' [q] %s\n' "$(rf_msg "Quit")"
|
||||||
|
_rf_ui_take_notices
|
||||||
|
printf '%s' "$RF_UI_NOTICE_TEXT"
|
||||||
|
printf '\n > '
|
||||||
|
|
||||||
|
choice="$(rf_read_key)" || {
|
||||||
|
printf '\n'; rf_ui_term_restore; trap - EXIT INT TERM; return 0
|
||||||
|
}
|
||||||
|
case "$choice" in
|
||||||
|
enter|space|up|down|left|right|escape) choice='' ;;
|
||||||
|
esac
|
||||||
|
printf '%s\n' "$choice"
|
||||||
|
|
||||||
|
case "$choice" in
|
||||||
|
1) printf '\n'; rf_ui_cooked rf_do_add ;;
|
||||||
|
2) rf_ui_apps ;;
|
||||||
|
3) rf_ui_workspaces ;;
|
||||||
|
4) rf_ui_settings ;;
|
||||||
|
q|Q) rf_ui_term_restore; trap - EXIT INT TERM; return 0 ;;
|
||||||
|
# Anything else (Enter, arrow keys, stray characters) just
|
||||||
|
# redraws. Escape is deliberately not a quit key, so a mistyped
|
||||||
|
# arrow key cannot close the menu.
|
||||||
|
*) ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
@@ -0,0 +1,389 @@
|
|||||||
|
# shellcheck shell=bash
|
||||||
|
#
|
||||||
|
# Workspaces: the feeds rdfeed is subscribed to, and the app menu entries they
|
||||||
|
# turn into.
|
||||||
|
#
|
||||||
|
# A workspace has an id made from its server name. Its settings are in
|
||||||
|
# ~/.config/rdfeed/workspaces/<id>, its apps in ~/.local/share/rdfeed/<id>
|
||||||
|
# (apps.tsv, rdp/<app>.rdp, icons/<app>.png), its password in the keyring.
|
||||||
|
# Each app gets ~/.local/share/applications/rdfeed-<id>--<app>.desktop, and
|
||||||
|
# the workspace a folder of its own in the app menu. With "Open files with
|
||||||
|
# the apps" on, the entries also name the file types, so file managers offer
|
||||||
|
# the apps under "Open with".
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# The keyring
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
rf_secret_get() {
|
||||||
|
rf_have secret-tool || return 1
|
||||||
|
secret-tool lookup service rdfeed workspace "$1" user "$2" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# rf_secret_set <id> <account> <label> <password>
|
||||||
|
# secret-tool keeps all of stdin, a trailing newline too, so printf and not
|
||||||
|
# a here-string. printf is a builtin: the password shows in no process list.
|
||||||
|
rf_secret_set() {
|
||||||
|
rf_have secret-tool || return 1
|
||||||
|
printf '%s' "$4" | secret-tool store --label="rdfeed: $3" service rdfeed workspace "$1" user "$2" \
|
||||||
|
> /dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
rf_secret_clear() {
|
||||||
|
rf_have secret-tool || return 0
|
||||||
|
secret-tool clear service rdfeed workspace "$1" > /dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Finding the feed
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# rf_discover <domain>
|
||||||
|
# Windows finds the feed for a work e-mail address in a TXT record of the
|
||||||
|
# domain, _msradc.<domain>. Whichever DNS tool is around asks for it.
|
||||||
|
rf_discover() {
|
||||||
|
local name="_msradc.$1" txt=''
|
||||||
|
rf_have resolvectl && txt="$(resolvectl query --legend=no -t TXT "$name" 2>/dev/null)"
|
||||||
|
[[ -z $txt ]] && rf_have dig && txt="$(dig +short TXT "$name" 2>/dev/null)"
|
||||||
|
[[ -z $txt ]] && rf_have host && txt="$(host -t TXT "$name" 2>/dev/null)"
|
||||||
|
[[ -z $txt ]] && rf_have drill && txt="$(drill TXT "$name" 2>/dev/null)"
|
||||||
|
[[ $txt =~ (https?://[^\"[:space:]]+) ]] || return 1
|
||||||
|
printf '%s\n' "${BASH_REMATCH[1]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# rf_feed_url <what was typed>
|
||||||
|
# A server name, an address or a work e-mail address, as the address of the
|
||||||
|
# feed, in RF_URL. 1: nothing typed, 2: no feed found for the e-mail address.
|
||||||
|
RF_URL=''
|
||||||
|
|
||||||
|
rf_feed_url() {
|
||||||
|
local in="$1" scheme rest host path
|
||||||
|
in="${in#"${in%%[![:space:]]*}"}"
|
||||||
|
in="${in%"${in##*[![:space:]]}"}"
|
||||||
|
RF_URL=''
|
||||||
|
[[ -n $in ]] || return 1
|
||||||
|
|
||||||
|
if [[ $in != */* && $in == *@* ]]; then
|
||||||
|
RF_URL="$(rf_discover "${in#*@}")" || return 2
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ $in == *://* ]] || in="https://$in"
|
||||||
|
scheme="${in%%://*}"
|
||||||
|
rest="${in#*://}"
|
||||||
|
host="${rest%%/*}"
|
||||||
|
path="${rest#"$host"}"
|
||||||
|
# The feed sits at the same place on every server. Whatever part of it
|
||||||
|
# was typed, the RD Web page included, leads there.
|
||||||
|
case "${path,,}" in
|
||||||
|
''|/|/rdweb|/rdweb/|/rdweb/feed|/rdweb/feed/|/rdweb/pages*) path=/RDWeb/Feed/webfeed.aspx ;;
|
||||||
|
*/webfeed) path+=.aspx ;;
|
||||||
|
esac
|
||||||
|
RF_URL="${scheme,,}://$host$path"
|
||||||
|
}
|
||||||
|
|
||||||
|
# rf_ws_id_for <url>
|
||||||
|
# The workspace that has this address, or a new id made from the server name.
|
||||||
|
rf_ws_id_for() {
|
||||||
|
local url="$1" id base n=2 host
|
||||||
|
rf_ws_ids
|
||||||
|
for id in "${RF_WS_IDS[@]}"; do
|
||||||
|
_rf_kv_lookup "$(rf_ws_file "$id")" Url
|
||||||
|
[[ ${RF_KV_VALUE,,} == "${url,,}" ]] && { printf '%s\n' "$id"; return; }
|
||||||
|
done
|
||||||
|
host="${url#*://}"
|
||||||
|
host="${host%%/*}"
|
||||||
|
host="${host%%:*}"
|
||||||
|
base="${host,,}"
|
||||||
|
base="${base//[^a-z0-9]/-}"
|
||||||
|
while [[ $base == *--* ]]; do base="${base//--/-}"; done
|
||||||
|
base="${base#-}"
|
||||||
|
base="${base%-}"
|
||||||
|
[[ -n $base ]] || base=workspace
|
||||||
|
id="$base"
|
||||||
|
while [[ -e $(rf_ws_file "$id") ]]; do
|
||||||
|
id="$base-$n"
|
||||||
|
n=$(( n + 1 ))
|
||||||
|
done
|
||||||
|
printf '%s\n' "$id"
|
||||||
|
}
|
||||||
|
|
||||||
|
# rf_split_user <typed>
|
||||||
|
# DOMAIN\name, name@domain or just a name, into RF_USER and RF_DOMAIN. The
|
||||||
|
# domain is "-" when the server's own should be taken, and empty for
|
||||||
|
# name@domain, which carries its domain.
|
||||||
|
rf_split_user() {
|
||||||
|
local in="$1"
|
||||||
|
if [[ $in == *\\* ]]; then
|
||||||
|
RF_DOMAIN="${in%%\\*}"
|
||||||
|
RF_USER="${in#*\\}"
|
||||||
|
elif [[ $in == *@* ]]; then
|
||||||
|
RF_DOMAIN=''
|
||||||
|
RF_USER="$in"
|
||||||
|
else
|
||||||
|
RF_DOMAIN='-'
|
||||||
|
RF_USER="$in"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Downloading
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# rf_fetch <url> <user> <domain or -> <dir> <password> [quiet]
|
||||||
|
# Runs rdfeed-fetch into <dir>, printing each app as it arrives unless quiet.
|
||||||
|
# Leaves RF_FETCH_NAME, RF_FETCH_DOMAIN, RF_FETCH_COUNT and, when it failed,
|
||||||
|
# RF_FETCH_ERROR (the reason, already translated).
|
||||||
|
rf_fetch() {
|
||||||
|
local url="$1" user="$2" domain="$3" out="$4" password="$5" quiet="${6:-}"
|
||||||
|
local kind a b c rc=1 detail
|
||||||
|
local -a args=(--url "$url" --user "$user" --out "$out")
|
||||||
|
[[ $domain != - ]] && args+=(--domain "$domain")
|
||||||
|
|
||||||
|
RF_FETCH_NAME='' RF_FETCH_DOMAIN='' RF_FETCH_COUNT=0 RF_FETCH_ERROR=''
|
||||||
|
while IFS=$'\t' read -r kind a b c; do
|
||||||
|
case "$kind" in
|
||||||
|
workspace) RF_FETCH_NAME="$a" ;;
|
||||||
|
domain) RF_FETCH_DOMAIN="$a" ;;
|
||||||
|
app)
|
||||||
|
RF_FETCH_COUNT=$(( RF_FETCH_COUNT + 1 ))
|
||||||
|
[[ -z $quiet ]] && printf ' %s✔%s %s\n' "$RF_C_GREEN" "$RF_C_RESET" "$c"
|
||||||
|
;;
|
||||||
|
skip)
|
||||||
|
[[ -z $quiet ]] && printf ' %s✘%s %s %s(%s)%s\n' "$RF_C_RED" "$RF_C_RESET" "$a" "$RF_C_DIM" "$b" "$RF_C_RESET"
|
||||||
|
;;
|
||||||
|
exit) rc="$a" ;;
|
||||||
|
esac
|
||||||
|
done < <("$RF_FETCH" "${args[@]}" <<< "$password" 2> "$out/.error"; printf 'exit\t%s\n' "$?")
|
||||||
|
|
||||||
|
(( rc == 0 )) && { rm -f "$out/.error"; return 0; }
|
||||||
|
detail="$(tail -n1 "$out/.error" 2>/dev/null)"
|
||||||
|
detail="${detail#rdfeed-fetch: }"
|
||||||
|
case "$rc" in
|
||||||
|
10) RF_FETCH_ERROR="$(rf_msg "The server did not accept the user name or password.")" ;;
|
||||||
|
11) RF_FETCH_ERROR="$(rf_msg "Could not reach the server: %s" "$detail")" ;;
|
||||||
|
12) RF_FETCH_ERROR="$(rf_msg "There is no RD Web feed at %s." "$url")" ;;
|
||||||
|
*) RF_FETCH_ERROR="$(rf_msg "Could not load the apps: %s" "${detail:-$rc}")" ;;
|
||||||
|
esac
|
||||||
|
return "$rc"
|
||||||
|
}
|
||||||
|
|
||||||
|
# rf_stage_dir
|
||||||
|
# A fresh directory next to the workspaces, so moving it in place is a rename.
|
||||||
|
rf_stage_dir() {
|
||||||
|
mkdir -p "$RF_DATADIR" && mktemp -d "$RF_DATADIR/.new.XXXXXX"
|
||||||
|
}
|
||||||
|
|
||||||
|
# rf_ws_install <id> <stage dir>
|
||||||
|
rf_ws_install() {
|
||||||
|
local dir="$RF_DATADIR/$1"
|
||||||
|
rm -f "$2/.error"
|
||||||
|
rm -rf "$dir.old"
|
||||||
|
[[ -d $dir ]] && mv "$dir" "$dir.old"
|
||||||
|
mv "$2" "$dir" || { [[ -d $dir.old ]] && mv "$dir.old" "$dir"; return 1; }
|
||||||
|
rm -rf "$dir.old"
|
||||||
|
rf_ws_entries "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# rf_ws_apps_load <id>
|
||||||
|
# The apps of a workspace in RF_APP_SLUGS, RF_APP_TYPES, RF_APP_TITLES and
|
||||||
|
# RF_APP_EXTS (the file types it opens, as "docx doc rtf").
|
||||||
|
RF_APP_SLUGS=() RF_APP_TYPES=() RF_APP_TITLES=() RF_APP_EXTS=()
|
||||||
|
|
||||||
|
rf_ws_apps_load() {
|
||||||
|
local slug type title exts file="$RF_DATADIR/$1/apps.tsv"
|
||||||
|
RF_APP_SLUGS=() RF_APP_TYPES=() RF_APP_TITLES=() RF_APP_EXTS=()
|
||||||
|
[[ -r $file ]] || return 0
|
||||||
|
while IFS=$'\t' read -r slug type title exts; do
|
||||||
|
[[ -n $slug ]] || continue
|
||||||
|
RF_APP_SLUGS+=("$slug") RF_APP_TYPES+=("$type") RF_APP_TITLES+=("$title") RF_APP_EXTS+=("$exts")
|
||||||
|
done < "$file"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# The app menu
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
_rf_desktop_value() {
|
||||||
|
local v="${1//\\/\\\\}"
|
||||||
|
v="${v//$'\n'/ }"
|
||||||
|
printf '%s' "${v//$'\r'/}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# The command, as the Exec line of an entry wants it.
|
||||||
|
_rf_exec_path() {
|
||||||
|
if [[ $RF_SELF =~ ^[A-Za-z0-9_./+-]+$ ]]; then
|
||||||
|
printf '%s' "$RF_SELF"
|
||||||
|
else
|
||||||
|
local p="${RF_SELF//\\/\\\\\\\\}"
|
||||||
|
p="${p//\"/\\\\\"}"
|
||||||
|
p="${p//\$/\\\\\$}"
|
||||||
|
p="${p//\`/\\\\\`}"
|
||||||
|
printf '"%s"' "$p"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# _rf_mime_types <ext>...
|
||||||
|
# The MIME types of the file extensions, as a MimeType= value, from the
|
||||||
|
# shared-mime-info database every desktop uses.
|
||||||
|
_rf_mime_types() {
|
||||||
|
local f
|
||||||
|
local -a globs=()
|
||||||
|
for f in "$RF_XDG_DATA/mime/globs2" /usr/local/share/mime/globs2 /usr/share/mime/globs2; do
|
||||||
|
[[ -r $f ]] && globs+=("$f")
|
||||||
|
done
|
||||||
|
(( ${#globs[@]} )) || return 0
|
||||||
|
awk -F: -v want=" $* " '
|
||||||
|
BEGIN { want = tolower(want) }
|
||||||
|
/^#/ { next }
|
||||||
|
{
|
||||||
|
glob = tolower($3)
|
||||||
|
if (glob !~ /^\*\.[a-z0-9_+-]+$/) next
|
||||||
|
if (index(want, " " substr(glob, 3) " ") && !seen[$2]++) out = out $2 ";"
|
||||||
|
}
|
||||||
|
END { printf "%s", out }
|
||||||
|
' "${globs[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# rf_menu_refresh
|
||||||
|
# KDE reads the app menu from a cache, which needs a nudge. GNOME and the rest
|
||||||
|
# watch the folders themselves.
|
||||||
|
rf_menu_refresh() {
|
||||||
|
if rf_have kbuildsycoca6; then
|
||||||
|
kbuildsycoca6 > /dev/null 2>&1 || true
|
||||||
|
elif rf_have kbuildsycoca5; then
|
||||||
|
kbuildsycoca5 > /dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
rf_have update-desktop-database && update-desktop-database "$RF_APPDIR" > /dev/null 2>&1
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# rf_ws_entries <id>
|
||||||
|
# An entry for each app, a folder for the workspace, and nothing left of apps
|
||||||
|
# the feed no longer has.
|
||||||
|
rf_ws_entries() {
|
||||||
|
local id="$1" i slug title icon comment file keywords mime openwith=no args
|
||||||
|
local -A keep=()
|
||||||
|
|
||||||
|
rf_ws_load "$id"
|
||||||
|
rf_ws_apps_load "$id"
|
||||||
|
mkdir -p "$RF_APPDIR" "$RF_MENUDIR" "$RF_DIRDIR" || return 1
|
||||||
|
_rf_kv_lookup "$RF_CONFIG" OpenWith yes
|
||||||
|
rf_is_true "$RF_KV_VALUE" && openwith=yes
|
||||||
|
|
||||||
|
keywords="$(_rf_desktop_value "$WS_NAME")"
|
||||||
|
keywords="${keywords//;/\\;}"
|
||||||
|
for i in "${!RF_APP_SLUGS[@]}"; do
|
||||||
|
slug="${RF_APP_SLUGS[i]}"
|
||||||
|
title="${RF_APP_TITLES[i]}"
|
||||||
|
icon="$RF_DATADIR/$id/icons/$slug.png"
|
||||||
|
[[ -f $icon ]] || icon=rdfeed
|
||||||
|
if [[ ${RF_APP_TYPES[i]} == Desktop ]]; then
|
||||||
|
comment="$(rf_msg "Remote desktop of %s" "$WS_NAME")"
|
||||||
|
else
|
||||||
|
comment="$(rf_msg "%s from %s" "$title" "$WS_NAME")"
|
||||||
|
fi
|
||||||
|
mime='' args=''
|
||||||
|
if [[ $openwith == yes && -n ${RF_APP_EXTS[i]} ]]; then
|
||||||
|
# shellcheck disable=SC2086 # one extension per argument
|
||||||
|
mime="$(_rf_mime_types ${RF_APP_EXTS[i]})"
|
||||||
|
[[ -n $mime ]] && args=' %f'
|
||||||
|
fi
|
||||||
|
file="$RF_APPDIR/rdfeed-$id--$slug.desktop"
|
||||||
|
keep[$file]=1
|
||||||
|
cat > "$file" <<- EOF
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=$(_rf_desktop_value "$title")
|
||||||
|
GenericName=$(_rf_desktop_value "$WS_NAME")
|
||||||
|
Comment=$(_rf_desktop_value "$comment")
|
||||||
|
Exec=$(_rf_exec_path) run $id/$slug$args
|
||||||
|
Icon=$icon
|
||||||
|
Terminal=false
|
||||||
|
StartupNotify=false
|
||||||
|
Categories=X-rdfeed-$id;
|
||||||
|
Keywords=RemoteApp;RDP;$keywords;
|
||||||
|
EOF
|
||||||
|
[[ -n $mime ]] && printf 'MimeType=%s\n' "$mime" >> "$file"
|
||||||
|
done
|
||||||
|
|
||||||
|
for file in "$RF_APPDIR"/rdfeed-"$id"--*.desktop; do
|
||||||
|
[[ -e $file && -z ${keep[$file]:-} ]] && rm -f "$file"
|
||||||
|
done
|
||||||
|
|
||||||
|
cat > "$RF_MENUDIR/rdfeed-$id.menu" <<- EOF
|
||||||
|
<!DOCTYPE Menu PUBLIC "-//freedesktop//DTD Menu 1.0//EN"
|
||||||
|
"http://www.freedesktop.org/standards/menu-spec/menu-1.0.dtd">
|
||||||
|
<!-- Written by rdfeed: the folder of the workspace $id. -->
|
||||||
|
<Menu>
|
||||||
|
<Name>Applications</Name>
|
||||||
|
<Menu>
|
||||||
|
<Name>rdfeed-$id</Name>
|
||||||
|
<Directory>rdfeed-$id.directory</Directory>
|
||||||
|
<Include>
|
||||||
|
<Category>X-rdfeed-$id</Category>
|
||||||
|
</Include>
|
||||||
|
</Menu>
|
||||||
|
</Menu>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > "$RF_DIRDIR/rdfeed-$id.directory" <<- EOF
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Directory
|
||||||
|
Name=$(_rf_desktop_value "$WS_NAME")
|
||||||
|
Icon=rdfeed
|
||||||
|
EOF
|
||||||
|
|
||||||
|
rf_menu_refresh
|
||||||
|
}
|
||||||
|
|
||||||
|
# rf_ws_entries_all
|
||||||
|
# The entries of every workspace again, after a setting that shows in them.
|
||||||
|
rf_ws_entries_all() {
|
||||||
|
local id
|
||||||
|
rf_ws_ids
|
||||||
|
for id in "${RF_WS_IDS[@]}"; do
|
||||||
|
rf_ws_entries "$id"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# rf_ws_purge <id>
|
||||||
|
# Everything of a workspace: entries, folder, apps, settings, password, logs.
|
||||||
|
rf_ws_purge() {
|
||||||
|
local id="$1" file
|
||||||
|
for file in "$RF_APPDIR"/rdfeed-"$id"--*.desktop; do
|
||||||
|
[[ -e $file ]] && rm -f "$file"
|
||||||
|
done
|
||||||
|
rm -f "$RF_MENUDIR/rdfeed-$id.menu" "$RF_DIRDIR/rdfeed-$id.directory"
|
||||||
|
rm -rf "${RF_DATADIR:?}/$id"
|
||||||
|
rm -f "$(rf_ws_file "$id")"
|
||||||
|
for file in "$RF_CACHEDIR/$id"--*.log; do
|
||||||
|
[[ -e $file ]] && rm -f "$file"
|
||||||
|
done
|
||||||
|
rf_secret_clear "$id"
|
||||||
|
rf_menu_refresh
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Refreshing every day
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
rf_timer_available() {
|
||||||
|
rf_have systemctl || return 1
|
||||||
|
systemctl --user cat "$RF_UNIT_TIMER" > /dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
rf_timer_enabled() {
|
||||||
|
rf_have systemctl || return 1
|
||||||
|
[[ $(systemctl --user is-enabled "$RF_UNIT_TIMER" 2>/dev/null) == enabled ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# rf_timer_set yes|no
|
||||||
|
rf_timer_set() {
|
||||||
|
rf_timer_available || return 1
|
||||||
|
if [[ $1 == yes ]]; then
|
||||||
|
systemctl --user enable --now "$RF_UNIT_TIMER" > /dev/null 2>&1
|
||||||
|
else
|
||||||
|
systemctl --user disable --now "$RF_UNIT_TIMER" > /dev/null 2>&1
|
||||||
|
fi
|
||||||
|
}
|
||||||
Executable
+321
@@ -0,0 +1,321 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# rdfeed: the RemoteApps of your company in the Linux app menu
|
||||||
|
#
|
||||||
|
# Companies publish their Windows apps (Word, Excel, their own tools) as an RD
|
||||||
|
# Web Access feed. Windows subscribes to it under "RemoteApp and Desktop
|
||||||
|
# Connections" and puts the apps in the start menu. rdfeed does the same on
|
||||||
|
# Linux: each app gets an entry in the app menu and opens as a window of its
|
||||||
|
# own, through FreeRDP.
|
||||||
|
#
|
||||||
|
# This is the front end: the menu and the commands. Signing in to the feed and
|
||||||
|
# downloading the apps is rdfeed-fetch's job, and rdfeed-hook.so goes into
|
||||||
|
# FreeRDP to work around two of its problems (see launch.sh).
|
||||||
|
#
|
||||||
|
# Copyright (C) 2026 Felitendo
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
RF_SELF="$(readlink -f "${BASH_SOURCE[0]}")"
|
||||||
|
RF_LIBDIR="${RF_LIBDIR:-@LIBDIR@}"
|
||||||
|
|
||||||
|
for _mod in common config workspace launch menu; do
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
if ! source "$RF_LIBDIR/$_mod.sh"; then
|
||||||
|
printf 'rdfeed: cannot load %s/%s.sh\n' "$RF_LIBDIR" "$_mod" >&2
|
||||||
|
exit 14
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
unset _mod
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Commands
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# rf_prompt <question> [default]
|
||||||
|
# A line typed in, in RF_ANSWER. Without a terminal it is read from stdin, so
|
||||||
|
# scripts can pipe the answers in.
|
||||||
|
RF_ANSWER=''
|
||||||
|
|
||||||
|
rf_prompt() {
|
||||||
|
RF_ANSWER=''
|
||||||
|
if [[ -t 0 ]]; then
|
||||||
|
IFS= read -rep " $1 " -i "${2:-}" RF_ANSWER || return 1
|
||||||
|
else
|
||||||
|
IFS= read -r RF_ANSWER || return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# rf_prompt_password <question>
|
||||||
|
rf_prompt_password() {
|
||||||
|
RF_ANSWER=''
|
||||||
|
if [[ -t 0 ]]; then
|
||||||
|
IFS= read -rsp " $1 " RF_ANSWER || return 1
|
||||||
|
printf '\n'
|
||||||
|
else
|
||||||
|
IFS= read -r RF_ANSWER || return 1
|
||||||
|
fi
|
||||||
|
[[ -n $RF_ANSWER ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# rf_signin <id or ""> <url> <user as typed> <password>
|
||||||
|
# Signs in, downloads the apps and puts them in the app menu. With an empty
|
||||||
|
# id a new workspace is made, or the one that already has this address.
|
||||||
|
rf_signin() {
|
||||||
|
local id="$1" url="$2" typed="$3" password="$4" stage old_account first=0 domain
|
||||||
|
|
||||||
|
[[ -n $id ]] || id="$(rf_ws_id_for "$url")"
|
||||||
|
rf_ws_ids
|
||||||
|
(( ${#RF_WS_IDS[@]} == 0 )) && first=1
|
||||||
|
old_account=''
|
||||||
|
if [[ -e $(rf_ws_file "$id") ]]; then
|
||||||
|
rf_ws_load "$id"
|
||||||
|
old_account="$WS_ACCOUNT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rf_split_user "$typed"
|
||||||
|
stage="$(rf_stage_dir)" || { rf_bad "$(rf_msg "Could not write to %s." "$RF_DATADIR")"; return 1; }
|
||||||
|
|
||||||
|
local host="${url#*://}"
|
||||||
|
rf_say " $(rf_msg "Signing in to %s…" "${host%%/*}")"
|
||||||
|
if ! rf_fetch "$url" "$RF_USER" "$RF_DOMAIN" "$stage" "$password"; then
|
||||||
|
rm -rf "$stage"
|
||||||
|
rf_bad "$RF_FETCH_ERROR"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
domain="$RF_DOMAIN"
|
||||||
|
[[ $domain == - ]] && domain="$RF_FETCH_DOMAIN"
|
||||||
|
rf_ws_set "$id" Url "$url"
|
||||||
|
rf_ws_set "$id" Name "$RF_FETCH_NAME"
|
||||||
|
rf_ws_set "$id" User "$RF_USER"
|
||||||
|
rf_ws_set "$id" Domain "$domain"
|
||||||
|
rf_ws_set "$id" Refreshed "$(date +%s)"
|
||||||
|
rf_ws_load "$id"
|
||||||
|
|
||||||
|
[[ -n $old_account && $old_account != "$WS_ACCOUNT" ]] && rf_secret_clear "$id"
|
||||||
|
if ! rf_secret_set "$id" "$WS_ACCOUNT" "$WS_NAME" "$password"; then
|
||||||
|
rf_note "$(rf_msg "The password could not be saved in the keyring, so each app asks for it.")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rf_ws_install "$id" "$stage" || { rf_bad "$(rf_msg "Could not write to %s." "$RF_DATADIR")"; return 1; }
|
||||||
|
|
||||||
|
# New apps should show up by themselves, as on Windows.
|
||||||
|
if (( first )) && [[ $(rf_config_get AutoRefresh yes) == yes ]]; then
|
||||||
|
rf_timer_set yes || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( RF_FETCH_COUNT == 1 )); then
|
||||||
|
rf_ok "$(rf_msg "1 app from %s is now in your app menu." "$WS_NAME")"
|
||||||
|
else
|
||||||
|
rf_ok "$(rf_msg "%d apps from %s are now in your app menu." "$RF_FETCH_COUNT" "$WS_NAME")"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
rf_do_add() {
|
||||||
|
local input='' user='' url rc
|
||||||
|
|
||||||
|
while (( $# )); do
|
||||||
|
case "$1" in
|
||||||
|
--user) user="${2:-}"; shift ;;
|
||||||
|
*) input="$1" ;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z $input ]]; then
|
||||||
|
rf_say " $(rf_msg "Your IT department has the address. A work e-mail address often works too.")"
|
||||||
|
rf_prompt "$(rf_msg "Feed address, server or e-mail:")" || return 1
|
||||||
|
input="$RF_ANSWER"
|
||||||
|
fi
|
||||||
|
rf_feed_url "$input"
|
||||||
|
rc=$?
|
||||||
|
case "$rc" in
|
||||||
|
1) return 1 ;;
|
||||||
|
2) rf_bad "$(rf_msg "No feed is registered for %s. Ask your IT department for the address." "$input")"; return 1 ;;
|
||||||
|
esac
|
||||||
|
url="$RF_URL"
|
||||||
|
|
||||||
|
if [[ -z $user ]]; then
|
||||||
|
rf_prompt "$(rf_msg "User name (DOMAIN\\name or name@domain):")" || return 1
|
||||||
|
user="$RF_ANSWER"
|
||||||
|
fi
|
||||||
|
[[ -n $user ]] || return 1
|
||||||
|
rf_prompt_password "$(rf_msg "Password:")" || return 1
|
||||||
|
|
||||||
|
rf_signin '' "$url" "$user" "$RF_ANSWER"
|
||||||
|
}
|
||||||
|
|
||||||
|
# rf_do_signin_again <id>
|
||||||
|
# Another password or another user for a workspace.
|
||||||
|
rf_do_signin_again() {
|
||||||
|
local id="$1"
|
||||||
|
rf_ws_load "$id"
|
||||||
|
rf_prompt "$(rf_msg "User name (DOMAIN\\name or name@domain):")" "$WS_ACCOUNT" || return 1
|
||||||
|
[[ -n $RF_ANSWER ]] || return 1
|
||||||
|
local user="$RF_ANSWER"
|
||||||
|
rf_prompt_password "$(rf_msg "Password:")" || return 1
|
||||||
|
rf_signin "$id" "$WS_URL" "$user" "$RF_ANSWER"
|
||||||
|
}
|
||||||
|
|
||||||
|
# rf_do_refresh [id] [--quiet]
|
||||||
|
# The quiet one is the daily timer's: no questions, and a notification only
|
||||||
|
# when somebody has to do something.
|
||||||
|
rf_do_refresh() {
|
||||||
|
local quiet='' only='' id password stage rc failed=0
|
||||||
|
while (( $# )); do
|
||||||
|
case "$1" in
|
||||||
|
--quiet) quiet=1 ;;
|
||||||
|
*) only="$1" ;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
rf_ws_ids
|
||||||
|
if (( ${#RF_WS_IDS[@]} == 0 )); then
|
||||||
|
[[ -z $quiet ]] && rf_note "$(rf_msg "No workspace yet. Add one with \`%s add\`." "$RF_NAME")"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
for id in "${RF_WS_IDS[@]}"; do
|
||||||
|
[[ -n $only && $id != "$only" ]] && continue
|
||||||
|
rf_ws_load "$id"
|
||||||
|
password="$(rf_secret_get "$id" "$WS_ACCOUNT")" || password=''
|
||||||
|
if [[ -z $password ]]; then
|
||||||
|
if [[ -n $quiet || ! -t 0 ]]; then
|
||||||
|
[[ -n $quiet ]] && rf_notify "$WS_NAME" "$(rf_msg "No password saved for %s. Sign in again in rdfeed." "$WS_NAME")" dialog-warning
|
||||||
|
failed=1
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
rf_prompt_password "$(rf_msg "Password for %s:" "$WS_ACCOUNT")" || { failed=1; continue; }
|
||||||
|
password="$RF_ANSWER"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -z $quiet ]] && rf_say " $(rf_msg "Refreshing %s…" "$WS_NAME")"
|
||||||
|
stage="$(rf_stage_dir)" || return 1
|
||||||
|
rf_fetch "$WS_URL" "$WS_USER" "${WS_DOMAIN:-}" "$stage" "$password" "$quiet"
|
||||||
|
rc=$?
|
||||||
|
if (( rc != 0 )); then
|
||||||
|
rm -rf "$stage"
|
||||||
|
failed=1
|
||||||
|
if [[ -n $quiet ]]; then
|
||||||
|
# A server that is not reachable now will be tomorrow. A
|
||||||
|
# password that no longer works needs the user.
|
||||||
|
(( rc == 10 )) && rf_notify "$WS_NAME" "$(rf_msg "The password no longer works. Sign in again in rdfeed.")" dialog-warning
|
||||||
|
else
|
||||||
|
rf_bad "$RF_FETCH_ERROR"
|
||||||
|
fi
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
[[ -n $RF_FETCH_NAME ]] && rf_ws_set "$id" Name "$RF_FETCH_NAME"
|
||||||
|
rf_ws_set "$id" Refreshed "$(date +%s)"
|
||||||
|
rf_ws_install "$id" "$stage" || { failed=1; continue; }
|
||||||
|
[[ -z $quiet ]] && rf_ok "$(rf_msg "%s: %d apps" "$WS_NAME" "$RF_FETCH_COUNT")"
|
||||||
|
done
|
||||||
|
return "$failed"
|
||||||
|
}
|
||||||
|
|
||||||
|
rf_do_remove() {
|
||||||
|
local id="${1:-}"
|
||||||
|
if [[ -z $id || ! -e $(rf_ws_file "$id") ]]; then
|
||||||
|
rf_bad "$(rf_msg "Which workspace? See \`%s list\` for the names." "$RF_NAME")"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
rf_ws_load "$id"
|
||||||
|
rf_ws_purge "$id"
|
||||||
|
rf_ws_ids
|
||||||
|
(( ${#RF_WS_IDS[@]} == 0 )) && { rf_timer_set no || true; }
|
||||||
|
rf_ok "$(rf_msg "%s and its apps are removed." "$WS_NAME")"
|
||||||
|
}
|
||||||
|
|
||||||
|
rf_do_list() {
|
||||||
|
local id i
|
||||||
|
rf_ws_ids
|
||||||
|
if (( ${#RF_WS_IDS[@]} == 0 )); then
|
||||||
|
rf_note "$(rf_msg "No workspace yet. Add one with \`%s add\`." "$RF_NAME")"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
for id in "${RF_WS_IDS[@]}"; do
|
||||||
|
rf_ws_load "$id"
|
||||||
|
rf_ws_apps_load "$id"
|
||||||
|
printf '%s%s%s %s(%s, %s)%s\n' "$RF_C_BOLD" "$WS_NAME" "$RF_C_RESET" "$RF_C_DIM" "$id" "$WS_ACCOUNT" "$RF_C_RESET"
|
||||||
|
for i in "${!RF_APP_SLUGS[@]}"; do
|
||||||
|
printf ' %-36s %s\n' "$id/${RF_APP_SLUGS[i]}" "${RF_APP_TITLES[i]}"
|
||||||
|
done
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# rf_do_run <app or workspace/app> [file]
|
||||||
|
rf_do_run() {
|
||||||
|
local spec="${1:-}" file="${2:-}" id slug found=() i
|
||||||
|
if [[ -z $spec ]]; then
|
||||||
|
rf_bad "$(rf_msg "Which app? See \`%s list\`." "$RF_NAME")"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if [[ $spec == */* ]]; then
|
||||||
|
rf_launch "${spec%%/*}" "${spec#*/}" "$file"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
rf_ws_ids
|
||||||
|
for id in "${RF_WS_IDS[@]}"; do
|
||||||
|
rf_ws_apps_load "$id"
|
||||||
|
for i in "${!RF_APP_SLUGS[@]}"; do
|
||||||
|
[[ ${RF_APP_SLUGS[i]} == "$spec" ]] && found+=("$id")
|
||||||
|
done
|
||||||
|
done
|
||||||
|
case "${#found[@]}" in
|
||||||
|
0) rf_bad "$(rf_msg "There is no app %s. See \`%s list\`." "$spec" "$RF_NAME")"; return 1 ;;
|
||||||
|
1) slug="$spec"; rf_launch "${found[0]}" "$slug" "$file" ;;
|
||||||
|
*) rf_bad "$(rf_msg "Several workspaces have %s. Write it as WORKSPACE/APP, see \`%s list\`." "$spec" "$RF_NAME")"; return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
rf_do_help() {
|
||||||
|
cat <<- EOF
|
||||||
|
$RF_PRETTY $RF_VERSION
|
||||||
|
|
||||||
|
$(rf_msg "Usage: rdfeed [command]")
|
||||||
|
|
||||||
|
$(rf_msg "Commands:")
|
||||||
|
add [ADDRESS] $(rf_msg "Add the workspace of your company")
|
||||||
|
refresh [NAME] $(rf_msg "Load the apps again")
|
||||||
|
list $(rf_msg "List the workspaces and their apps")
|
||||||
|
run APP [FILE] $(rf_msg "Start an app, or open a file with it")
|
||||||
|
remove NAME $(rf_msg "Remove a workspace and its apps")
|
||||||
|
-h, --help $(rf_msg "Show this help")
|
||||||
|
-V, --version $(rf_msg "Show the version")
|
||||||
|
|
||||||
|
$(rf_msg "Without a command an interactive menu is shown.")
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Dispatch
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
main() {
|
||||||
|
local cmd="${1:-}"
|
||||||
|
[[ $# -gt 0 ]] && shift
|
||||||
|
|
||||||
|
case "$cmd" in
|
||||||
|
add|subscribe) rf_do_add "$@" ;;
|
||||||
|
refresh|sync) rf_do_refresh "$@" ;;
|
||||||
|
list|ls) rf_do_list ;;
|
||||||
|
run|start) rf_do_run "$@" ;;
|
||||||
|
remove|delete) rf_do_remove "$@" ;;
|
||||||
|
|
||||||
|
-h|--help|help) rf_do_help ;;
|
||||||
|
-V|--version) printf '%s %s\n' "$RF_NAME" "$RF_VERSION" ;;
|
||||||
|
|
||||||
|
'') rf_ui_menu ;;
|
||||||
|
*)
|
||||||
|
rf_bad "$(rf_msg "Unknown command: %s" "$cmd")"
|
||||||
|
printf '\n'
|
||||||
|
rf_do_help
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
Executable
+212
@@ -0,0 +1,212 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
# A fake RD Web Access server for the tests: the feed behind the NTLM login,
|
||||||
|
# the forms ticket it hands out, .rdp files and icons. It checks the NTLMv2
|
||||||
|
# answer for real, against one user and password.
|
||||||
|
#
|
||||||
|
# python3 tests/fake_rdweb.py [port] serve until Ctrl+C
|
||||||
|
#
|
||||||
|
# Copyright (C) 2026 Felitendo
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import hmac
|
||||||
|
import http.server
|
||||||
|
import os
|
||||||
|
import socketserver
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
|
import importlib.machinery # noqa: E402
|
||||||
|
import importlib.util # noqa: E402
|
||||||
|
|
||||||
|
# rdfeed-fetch has no .py ending, so it is loaded by path.
|
||||||
|
_loader = importlib.machinery.SourceFileLoader(
|
||||||
|
"rdfeed_fetch", os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "src", "fetch", "rdfeed-fetch"))
|
||||||
|
_spec = importlib.util.spec_from_loader("rdfeed_fetch", _loader)
|
||||||
|
fetch = importlib.util.module_from_spec(_spec)
|
||||||
|
_loader.exec_module(fetch)
|
||||||
|
|
||||||
|
DOMAIN = "CONTOSO"
|
||||||
|
USER = "jdoe"
|
||||||
|
PASSWORD = "Correct horse 1"
|
||||||
|
TOKEN = "5EC2E7F00DBA11ADE0F7EED5ACC0FFEE"
|
||||||
|
|
||||||
|
APPS = [
|
||||||
|
("winword-RemoteApps-CmsRdsh", "Word", "RemoteApp"),
|
||||||
|
("excel-RemoteApps-CmsRdsh", "Excel", "RemoteApp"),
|
||||||
|
("powerpnt-RemoteApps-CmsRdsh", "PowerPoint", "RemoteApp"),
|
||||||
|
("desktop-RemoteApps-CmsRdsh", "Contoso Desktop", "Desktop"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def extensions(alias):
|
||||||
|
"""Excel comes with its file types, as an administrator would enter them."""
|
||||||
|
if not alias.startswith("excel"):
|
||||||
|
return "<FileExtensions />"
|
||||||
|
return ('<FileExtensions><FileExtension Name=".xlsx" PrimaryHandler="True" />'
|
||||||
|
'<FileExtension Name=".CSV" PrimaryHandler="False" /></FileExtensions>')
|
||||||
|
|
||||||
|
|
||||||
|
def feed_xml():
|
||||||
|
resources = ""
|
||||||
|
for alias, title, kind in APPS:
|
||||||
|
resources += f"""
|
||||||
|
<Resource ID="{alias}" Alias="{alias}" Title="{title}" LastUpdated="2026-10-01T08:00:00Z" Type="{kind}" ShowByDefault="True">
|
||||||
|
<Icons>
|
||||||
|
<IconRaw FileType="Ico" FileURL="/RDWeb/Pages/rdp/{alias}.ico" />
|
||||||
|
<Icon32 Dimensions="32x32" FileType="Png" FileURL="/RDWeb/Pages/rdp/{alias}.png" />
|
||||||
|
</Icons>
|
||||||
|
{extensions(alias)}
|
||||||
|
<Folders><Folder Name="/" /></Folders>
|
||||||
|
<HostingTerminalServers>
|
||||||
|
<HostingTerminalServer>
|
||||||
|
<ResourceFile FileExtension=".rdp" URL="/RDWeb/Pages/rdp/cpub-{alias}.rdp" />
|
||||||
|
<TerminalServerRef Ref="RDSH01.contoso.test" />
|
||||||
|
</HostingTerminalServer>
|
||||||
|
</HostingTerminalServers>
|
||||||
|
</Resource>"""
|
||||||
|
return f"""<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ResourceCollection PubDate="2026-10-01T08:00:00Z" SchemaVersion="2.1" xmlns="http://schemas.microsoft.com/ts/2007/05/tswf">
|
||||||
|
<Publisher LastUpdated="2026-10-01T08:00:00Z" Name="Contoso Apps" ID="RDSH01.contoso.test" Description="">
|
||||||
|
<Resources>{resources}
|
||||||
|
</Resources>
|
||||||
|
</Publisher>
|
||||||
|
</ResourceCollection>
|
||||||
|
""".encode()
|
||||||
|
|
||||||
|
|
||||||
|
def rdp_file(alias, title, kind):
|
||||||
|
lines = [
|
||||||
|
"full address:s:RDBROKER.contoso.test",
|
||||||
|
"gatewayhostname:s:gateway.contoso.test",
|
||||||
|
"gatewayusagemethod:i:1",
|
||||||
|
"promptcredentialonce:i:0",
|
||||||
|
"prompt for credentials on client:i:1",
|
||||||
|
"drivestoredirect:s:*",
|
||||||
|
"devicestoredirect:s:*",
|
||||||
|
"camerastoredirect:s:*",
|
||||||
|
"loadbalanceinfo:s:tsv://MS Terminal Services Plugin.1.RemoteApps",
|
||||||
|
]
|
||||||
|
if kind == "RemoteApp":
|
||||||
|
lines += ["remoteapplicationmode:i:1", f"remoteapplicationprogram:s:||{alias.split('-')[0]}",
|
||||||
|
f"remoteapplicationname:s:{title}"]
|
||||||
|
return ("\r\n".join(lines) + "\r\n").encode("utf-16")
|
||||||
|
|
||||||
|
|
||||||
|
def ico_32bpp(size=48, rgba=(30, 110, 220, 255)):
|
||||||
|
"""An .ico with one 32-bit bitmap frame, the way Windows writes them."""
|
||||||
|
r, g, b, a = rgba
|
||||||
|
header = struct.pack("<IiiHHIIiiII", 40, size, size * 2, 1, 32, 0, 0, 0, 0, 0, 0)
|
||||||
|
pixels = bytes((b, g, r, a)) * (size * size)
|
||||||
|
mask = b"\x00" * (((size + 31) // 32) * 4 * size)
|
||||||
|
dib = header + pixels + mask
|
||||||
|
return (struct.pack("<HHH", 0, 1, 1)
|
||||||
|
+ struct.pack("<BBBBHHII", size, size, 0, 0, 1, 32, len(dib), 22) + dib)
|
||||||
|
|
||||||
|
|
||||||
|
def ntlm_challenge():
|
||||||
|
def av(kind, value):
|
||||||
|
data = value.encode("utf-16-le")
|
||||||
|
return struct.pack("<HH", kind, len(data)) + data
|
||||||
|
info = (av(2, DOMAIN) + av(1, "RDWEB01") + av(4, "contoso.test") + av(3, "rdweb01.contoso.test")
|
||||||
|
+ struct.pack("<HH", 7, 8) + struct.pack("<Q", 133000000000000000) + struct.pack("<HH", 0, 0))
|
||||||
|
server_challenge = os.urandom(8)
|
||||||
|
target = DOMAIN.encode("utf-16-le")
|
||||||
|
head = 56
|
||||||
|
message = (b"NTLMSSP\x00" + struct.pack("<I", 2) + struct.pack("<HHI", len(target), len(target), head)
|
||||||
|
+ struct.pack("<I", 0xE2898215) + server_challenge + b"\x00" * 8
|
||||||
|
+ struct.pack("<HHI", len(info), len(info), head + len(target)) + fetch.NTLM_VERSION
|
||||||
|
+ target + info)
|
||||||
|
return message, server_challenge
|
||||||
|
|
||||||
|
|
||||||
|
def ntlm_check(message, server_challenge):
|
||||||
|
"""True if the AUTHENTICATE message proves the password."""
|
||||||
|
def field(i):
|
||||||
|
length, _, offset = struct.unpack("<HHI", message[12 + 8 * i:20 + 8 * i])
|
||||||
|
return message[offset:offset + length]
|
||||||
|
nt, domain, user = field(1), field(2).decode("utf-16-le"), field(3).decode("utf-16-le")
|
||||||
|
if user.lower() != USER or domain.upper() != DOMAIN or len(nt) < 32:
|
||||||
|
return False
|
||||||
|
key = fetch.ntowfv2(user, domain, PASSWORD)
|
||||||
|
proof = hmac.new(key, server_challenge + nt[16:], "md5").digest()
|
||||||
|
return hmac.compare_digest(proof, nt[:16])
|
||||||
|
|
||||||
|
|
||||||
|
class Handler(http.server.BaseHTTPRequestHandler):
|
||||||
|
protocol_version = "HTTP/1.1"
|
||||||
|
|
||||||
|
def log_message(self, *args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def reply(self, status, body=b"", headers=None):
|
||||||
|
self.send_response(status)
|
||||||
|
for k, v in (headers or {}).items():
|
||||||
|
if isinstance(v, list):
|
||||||
|
for one in v:
|
||||||
|
self.send_header(k, one)
|
||||||
|
else:
|
||||||
|
self.send_header(k, v)
|
||||||
|
self.send_header("Content-Length", str(len(body)))
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(body)
|
||||||
|
|
||||||
|
def signed_in(self):
|
||||||
|
cookies = self.headers.get("Cookie", "")
|
||||||
|
return f"TSWAFeedAuthCookie={TOKEN}" in cookies
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
path = urllib.parse.urlsplit(self.path).path
|
||||||
|
lower = path.lower()
|
||||||
|
if lower == "/rdweb/feed/webfeed.aspx":
|
||||||
|
if self.signed_in():
|
||||||
|
return self.reply(200, feed_xml(), {"Content-Type": "application/x-msts-radc+xml; charset=utf-8"})
|
||||||
|
return self.reply(302, b"", {"Location": "/RDWeb/FeedLogin/WebFeedLogin.aspx"})
|
||||||
|
if lower == "/rdweb/feedlogin/webfeedlogin.aspx":
|
||||||
|
auth = self.headers.get("Authorization", "")
|
||||||
|
if auth.startswith("NTLM "):
|
||||||
|
message = base64.b64decode(auth[5:])
|
||||||
|
kind = struct.unpack("<I", message[8:12])[0]
|
||||||
|
if kind == 1:
|
||||||
|
challenge, self.server_challenge = ntlm_challenge()
|
||||||
|
return self.reply(401, b"", {"WWW-Authenticate": "NTLM " + base64.b64encode(challenge).decode()})
|
||||||
|
if kind == 3 and ntlm_check(message, getattr(self, "server_challenge", b"")):
|
||||||
|
return self.reply(200, TOKEN.encode(), {"Content-Type": "application/x-msts-webfeed-login; charset=utf-8"})
|
||||||
|
return self.reply(401, b"denied", {"WWW-Authenticate": ["NTLM", "Negotiate"], "Content-Type": "text/html"})
|
||||||
|
if lower.startswith("/rdweb/pages/rdp/"):
|
||||||
|
if not self.signed_in():
|
||||||
|
return self.reply(302, b"", {"Location": "/RDWeb/Pages/en-US/login.aspx?ReturnUrl=" + path})
|
||||||
|
name = path.rsplit("/", 1)[1]
|
||||||
|
for alias, title, kind in APPS:
|
||||||
|
if name == f"cpub-{alias}.rdp":
|
||||||
|
return self.reply(200, rdp_file(alias, title, kind), {"Content-Type": "application/x-rdp"})
|
||||||
|
if name == f"{alias}.ico":
|
||||||
|
return self.reply(200, ico_32bpp(), {"Content-Type": "image/x-icon"})
|
||||||
|
if name == f"{alias}.png":
|
||||||
|
return self.reply(404)
|
||||||
|
return self.reply(404, b"<html>not here</html>", {"Content-Type": "text/html"})
|
||||||
|
|
||||||
|
|
||||||
|
class Server(socketserver.ThreadingMixIn, http.server.HTTPServer):
|
||||||
|
daemon_threads = True
|
||||||
|
allow_reuse_address = True
|
||||||
|
|
||||||
|
|
||||||
|
def serve(port=0):
|
||||||
|
"""Starts the server in a thread. Returns it; its URL base is server.base."""
|
||||||
|
server = Server(("127.0.0.1", port), Handler)
|
||||||
|
server.base = f"http://127.0.0.1:{server.server_address[1]}"
|
||||||
|
threading.Thread(target=server.serve_forever, daemon=True).start()
|
||||||
|
return server
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
srv = serve(int(sys.argv[1]) if len(sys.argv) > 1 else 0)
|
||||||
|
print(srv.base + "/RDWeb/Feed/webfeed.aspx", flush=True)
|
||||||
|
try:
|
||||||
|
threading.Event().wait()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# The rdfeed command from start to end, against tests/fake_rdweb.py: add a
|
||||||
|
# workspace, list, refresh, start an app, remove it again.
|
||||||
|
#
|
||||||
|
# Everything happens in a temporary home. secret-tool, systemctl, notify-send,
|
||||||
|
# kbuildsycoca6 and xfreerdp3 are fakes on PATH, so the real keyring, timers,
|
||||||
|
# notifications and app menu stay untouched.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2026 Felitendo
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
tmp="$(mktemp -d)"
|
||||||
|
server=''
|
||||||
|
trap '[[ -n $server ]] && kill "$server" 2>/dev/null; rm -rf -- "$tmp"' EXIT
|
||||||
|
failed=0
|
||||||
|
|
||||||
|
export HOME="$tmp/home" XDG_CONFIG_HOME="$tmp/config" XDG_DATA_HOME="$tmp/data" XDG_CACHE_HOME="$tmp/cache"
|
||||||
|
export RF_LIBDIR="$root/src/lib" RF_FETCH="$root/src/fetch/rdfeed-fetch" RF_LOCALEDIR="$tmp/locale"
|
||||||
|
RF_HOOK="$(readlink -f "${RDFEED_HOOK:-$root/build/rdfeed-hook.so}")"
|
||||||
|
export RF_HOOK
|
||||||
|
export LANG=C LC_ALL=C NO_COLOR=1 XDG_CURRENT_DESKTOP=test XDG_RUNTIME_DIR="$tmp/run"
|
||||||
|
mkdir -p "$HOME" "$tmp/bin"
|
||||||
|
|
||||||
|
# The fakes. Each writes what it was asked into $tmp, as a log or a file.
|
||||||
|
cat > "$tmp/bin/secret-tool" <<- 'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
store="${0%/bin/*}/secrets"; mkdir -p "$store"
|
||||||
|
cmd="$1"; shift; ws=''
|
||||||
|
while (( $# )); do [[ $1 == workspace ]] && ws="$2"; shift; done
|
||||||
|
case "$cmd" in
|
||||||
|
store) cat > "$store/$ws" ;;
|
||||||
|
lookup) [[ -f $store/$ws ]] && cat "$store/$ws" ;;
|
||||||
|
clear) rm -f "$store/$ws" ;;
|
||||||
|
esac
|
||||||
|
EOF
|
||||||
|
cat > "$tmp/bin/xfreerdp3" <<- 'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
out="${0%/bin/*}"
|
||||||
|
cat > "$out/freerdp-args"
|
||||||
|
cp "$(head -n1 "$out/freerdp-args")" "$out/freerdp-rdp"
|
||||||
|
env > "$out/freerdp-env"
|
||||||
|
echo "[INFO] connected"
|
||||||
|
echo "[rdfeed] gateway-delay 12" >&2
|
||||||
|
exit "${FAKE_FREERDP_EXIT:-0}"
|
||||||
|
EOF
|
||||||
|
for fake in systemctl notify-send kbuildsycoca6 update-desktop-database; do
|
||||||
|
# shellcheck disable=SC2016 # the script expands when it runs, not here
|
||||||
|
printf '#!/usr/bin/env bash\necho "%s $*" >> "${0%%/bin/*}/calls"\n[[ $1 == --user && $2 == cat ]] && exit 1\nexit 0\n' "$fake" > "$tmp/bin/$fake"
|
||||||
|
done
|
||||||
|
chmod +x "$tmp/bin"/*
|
||||||
|
export PATH="$tmp/bin:$PATH"
|
||||||
|
|
||||||
|
rdfeed() { bash "$root/src/rdfeed" "$@"; }
|
||||||
|
|
||||||
|
check() {
|
||||||
|
if eval "$2"; then
|
||||||
|
printf 'ok %s\n' "$1"
|
||||||
|
else
|
||||||
|
printf 'FAIL %s\n' "$1"
|
||||||
|
failed=1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# The URL helpers, on their own.
|
||||||
|
url_helpers() (
|
||||||
|
source "$RF_LIBDIR/common.sh"; source "$RF_LIBDIR/config.sh"; source "$RF_LIBDIR/workspace.sh"
|
||||||
|
for pair in "rdweb.contoso.com|https://rdweb.contoso.com/RDWeb/Feed/webfeed.aspx" \
|
||||||
|
"office.contoso.com/rdweb/feed/webfeed|https://office.contoso.com/rdweb/feed/webfeed.aspx" \
|
||||||
|
"https://x.test/RDWeb/Pages/en-US/Default.aspx|https://x.test/RDWeb/Feed/webfeed.aspx" \
|
||||||
|
"HTTPS://x.test:8443/RDWeb/|https://x.test:8443/RDWeb/Feed/webfeed.aspx" \
|
||||||
|
"https://x.test/custom/feed.aspx|https://x.test/custom/feed.aspx"; do
|
||||||
|
rf_feed_url "${pair%%|*}"
|
||||||
|
[[ $RF_URL == "${pair#*|}" ]] || { echo " $RF_URL != ${pair#*|}"; exit 1; }
|
||||||
|
done
|
||||||
|
rf_split_user 'CONTOSO\jdoe'; [[ $RF_USER == jdoe && $RF_DOMAIN == CONTOSO ]] || exit 1
|
||||||
|
rf_split_user 'jdoe@contoso.com'; [[ $RF_USER == jdoe@contoso.com && -z $RF_DOMAIN ]] || exit 1
|
||||||
|
rf_split_user 'jdoe'; [[ $RF_USER == jdoe && $RF_DOMAIN == - ]] || exit 1
|
||||||
|
[[ $(rf_ws_id_for "https://Office.Contoso.COM/RDWeb/Feed/webfeed.aspx") == office-contoso-com ]] || exit 1
|
||||||
|
)
|
||||||
|
if url_helpers; then check "addresses, user names and ids" true; else check "addresses, user names and ids" false; fi
|
||||||
|
|
||||||
|
python3 "$root/tests/fake_rdweb.py" > "$tmp/url" &
|
||||||
|
server=$!
|
||||||
|
for _ in $(seq 50); do [[ -s $tmp/url ]] && break; sleep 0.1; done
|
||||||
|
url="$(< "$tmp/url")"
|
||||||
|
id=127-0-0-1
|
||||||
|
password="$(python3 -c 'import sys; sys.path.insert(0, sys.argv[1]); import fake_rdweb; print(fake_rdweb.PASSWORD)' "$root/tests")"
|
||||||
|
|
||||||
|
out="$(printf 'CONTOSO\\jdoe\nwrong\n' | rdfeed add "$url" 2>&1)"
|
||||||
|
rc=$?
|
||||||
|
check "a wrong password is refused" "[[ \$rc -ne 0 ]] && grep -q 'did not accept' <<< \"\$out\""
|
||||||
|
check "and leaves nothing behind" "[[ ! -e $XDG_CONFIG_HOME/rdfeed/workspaces/$id ]]"
|
||||||
|
|
||||||
|
out="$(printf 'jdoe\n%s\n' "$password" | rdfeed add "${url%/RDWeb/*}" 2>&1)"
|
||||||
|
check "add signs in and loads the apps" "grep -q '4 apps from Contoso Apps are now in your app menu' <<< \"\$out\""
|
||||||
|
ws="$XDG_CONFIG_HOME/rdfeed/workspaces/$id"
|
||||||
|
check "the workspace remembers the server's domain" "grep -qx 'Domain=CONTOSO' '$ws' && grep -qx 'User=jdoe' '$ws'"
|
||||||
|
check "the password is in the keyring, as typed" "[[ \$(< '$tmp/secrets/$id') == \"\$password\" ]]"
|
||||||
|
entry="$XDG_DATA_HOME/applications/rdfeed-$id--powerpnt.desktop"
|
||||||
|
check "each app has a menu entry" "[[ \$(ls '$XDG_DATA_HOME/applications' | wc -l) -eq 4 ]]"
|
||||||
|
check "which starts it" "grep -q 'Exec=$root/src/rdfeed run $id/powerpnt' '$entry'"
|
||||||
|
check "with its own icon" "grep -qx 'Icon=$XDG_DATA_HOME/rdfeed/$id/icons/powerpnt.png' '$entry'"
|
||||||
|
word="$XDG_DATA_HOME/applications/rdfeed-$id--winword.desktop"
|
||||||
|
if [[ -r /usr/share/mime/globs2 ]]; then
|
||||||
|
check "Word is offered for .docx files" "grep -q '^MimeType=.*application/vnd.openxmlformats-officedocument.wordprocessingml.document;' '$word' && grep -q 'run $id/winword %f\$' '$word'"
|
||||||
|
check "Excel for the types of the feed" "grep -qx 'MimeType=.*text/csv;.*' '$XDG_DATA_HOME/applications/rdfeed-$id--excel.desktop' || grep -q 'MimeType=.*csv' '$XDG_DATA_HOME/applications/rdfeed-$id--excel.desktop'"
|
||||||
|
fi
|
||||||
|
check "the desktop for none" "! grep -q MimeType '$XDG_DATA_HOME/applications/rdfeed-$id--desktop.desktop'"
|
||||||
|
if command -v desktop-file-validate > /dev/null; then
|
||||||
|
check "the entries are valid" "desktop-file-validate --no-hints '$XDG_DATA_HOME'/applications/*.desktop"
|
||||||
|
fi
|
||||||
|
check "the workspace has a folder" "grep -q '<Category>X-rdfeed-$id</Category>' '$XDG_CONFIG_HOME/menus/applications-merged/rdfeed-$id.menu' && grep -qx 'Name=Contoso Apps' '$XDG_DATA_HOME/desktop-directories/rdfeed-$id.directory'"
|
||||||
|
|
||||||
|
out="$(rdfeed list 2>&1)"
|
||||||
|
check "list shows the apps" "grep -q '$id/powerpnt *PowerPoint' <<< \"\$out\""
|
||||||
|
|
||||||
|
out="$(rdfeed refresh < /dev/null 2>&1)"
|
||||||
|
check "refresh takes the password from the keyring" "grep -q 'Contoso Apps: 4 apps' <<< \"\$out\""
|
||||||
|
|
||||||
|
rdfeed run powerpnt > /dev/null 2>&1
|
||||||
|
check "run hands FreeRDP the .rdp file first" "[[ \$(head -n1 '$tmp/freerdp-args') == '$XDG_DATA_HOME/rdfeed/$id/rdp/powerpnt.rdp' ]]"
|
||||||
|
check "with the user, domain and password" "grep -qx '/u:jdoe' '$tmp/freerdp-args' && grep -qx '/d:CONTOSO' '$tmp/freerdp-args' && grep -qxF \"/p:\$password\" '$tmp/freerdp-args'"
|
||||||
|
check "and the hook, with GFX left on" "grep -qx 'LD_PRELOAD=$RF_HOOK' '$tmp/freerdp-env' && ! grep -q 'RDFEED_NO_GFX' '$tmp/freerdp-env'"
|
||||||
|
check "the log has what FreeRDP said" "grep -q 'gateway-delay 12' '$XDG_CACHE_HOME/rdfeed/$id--powerpnt.log'"
|
||||||
|
check "a notification says a second prompt comes" "grep -q 'notify-send .*A second sign-in is coming' '$tmp/calls'"
|
||||||
|
|
||||||
|
mkdir -p "$HOME/Downloads" "$HOME/Documents/Reports"
|
||||||
|
: > "$HOME/Downloads/Report, final.docx"
|
||||||
|
rdfeed run "$id/winword" "$HOME/Downloads/Report, final.docx" > /dev/null 2>&1
|
||||||
|
check "a file opens through its shared folder" "grep -qxF 'remoteapplicationcmdline:s:\"\\\\tsclient\\Downloads\\Report, final.docx\"'\$'\\r' '$tmp/freerdp-rdp' && grep -qx '/drive:Downloads,$HOME/Downloads' '$tmp/freerdp-args'"
|
||||||
|
check "and its copy of the .rdp is gone after" "[[ -z \$(ls -A '$XDG_RUNTIME_DIR/rdfeed' 2>/dev/null) ]]"
|
||||||
|
check "and a notification names it" "grep -q 'notify-send .*Opening Report, final.docx in Word' '$tmp/calls'"
|
||||||
|
|
||||||
|
rdfeed run "$id/winword" "$HOME/nothing.docx" > /dev/null 2>&1
|
||||||
|
rc=$?
|
||||||
|
check "a missing file is refused" "[[ \$rc -ne 0 ]]"
|
||||||
|
|
||||||
|
# shellcheck disable=SC2016 # user-dirs.dirs wants $HOME as it is
|
||||||
|
printf 'XDG_DOCUMENTS_DIR="$HOME/Documents"\n' > "$XDG_CONFIG_HOME/user-dirs.dirs"
|
||||||
|
printf 'Share=documents\nGraphics=classic\nSound=no\nOpenWith=no\n' > "$XDG_CONFIG_HOME/rdfeed/config"
|
||||||
|
: > "$HOME/Documents/Reports/q3.xlsx"
|
||||||
|
rdfeed run "$id/excel" "$HOME/Documents/Reports/q3.xlsx" > /dev/null 2>&1
|
||||||
|
check "a file in the shared folder needs no drive of its own" "grep -qF 'tsclient\\Documents\\Reports\\q3.xlsx' '$tmp/freerdp-rdp' && [[ \$(grep -c '^/drive:' '$tmp/freerdp-args') -eq 1 ]]"
|
||||||
|
check "the settings reach FreeRDP" "grep -qx 'RDFEED_NO_GFX=1' '$tmp/freerdp-env' && ! grep -qx '/sound' '$tmp/freerdp-args'"
|
||||||
|
|
||||||
|
rdfeed refresh < /dev/null > /dev/null 2>&1
|
||||||
|
check "with Open with off the entries name no file types" "! grep -q MimeType '$word' && grep -q 'run $id/winword\$' '$word'"
|
||||||
|
|
||||||
|
FAKE_FREERDP_EXIT=131 rdfeed run "$id/powerpnt" > /dev/null 2>&1
|
||||||
|
rc=$?
|
||||||
|
check "a failed start says so" "[[ \$rc -ne 0 ]] && grep -q 'notify-send .*PowerPoint could not start' '$tmp/calls'"
|
||||||
|
|
||||||
|
rdfeed run nothing > /dev/null 2>&1
|
||||||
|
rc=$?
|
||||||
|
check "an unknown app is refused" "[[ \$rc -ne 0 ]]"
|
||||||
|
|
||||||
|
rdfeed remove "$id" > /dev/null 2>&1
|
||||||
|
check "remove takes everything away" "[[ ! -e '$ws' && ! -e '$tmp/secrets/$id' && ! -d '$XDG_DATA_HOME/rdfeed/$id' ]] && ! ls '$XDG_DATA_HOME'/applications/rdfeed-* > /dev/null 2>&1"
|
||||||
|
|
||||||
|
exit "$failed"
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
# rdfeed-fetch: NTLM against the test vectors of MS-NLMP, the feed, the .rdp
|
||||||
|
# files, the icons, and whole subscriptions against tests/fake_rdweb.py.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2026 Felitendo
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import hmac
|
||||||
|
import os
|
||||||
|
import struct
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
import zlib
|
||||||
|
|
||||||
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
sys.path.insert(0, HERE)
|
||||||
|
import fake_rdweb # noqa: E402
|
||||||
|
|
||||||
|
fetch = fake_rdweb.fetch
|
||||||
|
FETCH = os.path.join(HERE, "..", "src", "fetch", "rdfeed-fetch")
|
||||||
|
|
||||||
|
|
||||||
|
def png_info(png):
|
||||||
|
"""Width, height and the RGBA bytes of a PNG written by png_encode."""
|
||||||
|
assert png[:8] == fetch.PNG_SIGNATURE
|
||||||
|
width, height = struct.unpack(">II", png[16:24])
|
||||||
|
pos, idat = 8, b""
|
||||||
|
while pos < len(png):
|
||||||
|
length = struct.unpack(">I", png[pos:pos + 4])[0]
|
||||||
|
kind = png[pos + 4:pos + 8]
|
||||||
|
if kind == b"IDAT":
|
||||||
|
idat += png[pos + 8:pos + 8 + length]
|
||||||
|
pos += 12 + length
|
||||||
|
raw = zlib.decompress(idat)
|
||||||
|
return width, height, raw
|
||||||
|
|
||||||
|
|
||||||
|
class Ntlm(unittest.TestCase):
|
||||||
|
def test_md4(self):
|
||||||
|
self.assertEqual(fetch.md4(b"").hex(), "31d6cfe0d16ae931b73c59d7e0c089c0")
|
||||||
|
self.assertEqual(fetch.md4(b"abc").hex(), "a448017aaf21d8525fc10ae87aa6729d")
|
||||||
|
self.assertEqual(fetch.md4(b"1234567890" * 8).hex(), "e33b4ddc9c38f2199c3e7b164fcc0536")
|
||||||
|
|
||||||
|
# MS-NLMP 4.2.4: User, Domain, Password, server challenge 0123456789abcdef,
|
||||||
|
# client challenge aa..aa, time 0, target info Domain and Server.
|
||||||
|
def test_ntlmv2_vectors(self):
|
||||||
|
key = fetch.ntowfv2("User", "Domain", "Password")
|
||||||
|
self.assertEqual(key.hex(), "0c868a403bfd7a93a3001ef22ef02e3f")
|
||||||
|
server, client = bytes.fromhex("0123456789abcdef"), b"\xaa" * 8
|
||||||
|
lm = hmac.new(key, server + client, "md5").digest() + client
|
||||||
|
self.assertEqual(lm.hex(), "86c35097ac9cec102554764a57cccc19aaaaaaaaaaaaaaaa")
|
||||||
|
info = (struct.pack("<HH", 2, 12) + "Domain".encode("utf-16-le")
|
||||||
|
+ struct.pack("<HH", 1, 12) + "Server".encode("utf-16-le") + b"\x00" * 4)
|
||||||
|
nt = fetch.ntlmv2_response(key, server, client, b"\x00" * 8, info)
|
||||||
|
self.assertEqual(nt[:16].hex(), "68cd0ab851e51c96aabc927bebef6a1c")
|
||||||
|
|
||||||
|
def test_domain_from_server(self):
|
||||||
|
challenge, server_challenge = fake_rdweb.ntlm_challenge()
|
||||||
|
message, domain = fetch.ntlm_authenticate(challenge, "jdoe", None, fake_rdweb.PASSWORD)
|
||||||
|
self.assertEqual(domain, "CONTOSO")
|
||||||
|
self.assertTrue(fake_rdweb.ntlm_check(message, server_challenge))
|
||||||
|
message, _ = fetch.ntlm_authenticate(challenge, "jdoe", None, "wrong")
|
||||||
|
self.assertFalse(fake_rdweb.ntlm_check(message, server_challenge))
|
||||||
|
|
||||||
|
|
||||||
|
class Feed(unittest.TestCase):
|
||||||
|
def test_parse(self):
|
||||||
|
name, apps = fetch.parse_feed(fake_rdweb.feed_xml())
|
||||||
|
self.assertEqual(name, "Contoso Apps")
|
||||||
|
self.assertEqual([a["slug"] for a in apps], ["winword", "excel", "powerpnt", "desktop"])
|
||||||
|
self.assertEqual([a["type"] for a in apps], ["RemoteApp"] * 3 + ["Desktop"])
|
||||||
|
self.assertEqual(apps[0]["rdp"], "/RDWeb/Pages/rdp/cpub-winword-RemoteApps-CmsRdsh.rdp")
|
||||||
|
self.assertEqual(apps[1]["types"], ["xlsx", "csv"])
|
||||||
|
self.assertEqual(apps[0]["types"], [])
|
||||||
|
|
||||||
|
def test_known_types(self):
|
||||||
|
self.assertEqual(fetch.known_types("||winword", "Word", "winword-x")[:2], ["docx", "docm"])
|
||||||
|
self.assertEqual(fetch.known_types("||powerpoint", "PowerPoint", ""), "pptx pptm ppt potx potm ppsx pps odp".split())
|
||||||
|
self.assertEqual(fetch.known_types("||trueview", "DWG TrueView", "trueview-x"), ["dwg", "dxf"])
|
||||||
|
self.assertEqual(fetch.known_types("||pdf24", "PDF24", ""), ["pdf"])
|
||||||
|
self.assertEqual(fetch.known_types("||wordpad", "WordPad", "wordpad-x"), [])
|
||||||
|
self.assertEqual(fetch.known_types("||pwchange", "Passwort ändern", "pwchange-x"), [])
|
||||||
|
|
||||||
|
def test_not_a_feed(self):
|
||||||
|
for data in (b"<html><body>login</body></html>", b"", b"<RDWAPage/>"):
|
||||||
|
with self.assertRaises(fetch.FeedError):
|
||||||
|
fetch.parse_feed(data)
|
||||||
|
|
||||||
|
def test_slugs(self):
|
||||||
|
taken = set()
|
||||||
|
self.assertEqual(fetch.make_slug("Passwort ändern", taken), "passwort-andern")
|
||||||
|
self.assertEqual(fetch.make_slug("Passwort ändern", taken), "passwort-andern-2")
|
||||||
|
self.assertEqual(fetch.make_slug("计算器", taken), "app")
|
||||||
|
self.assertEqual(fetch.make_slug("!!!", taken), "app-2")
|
||||||
|
self.assertEqual(fetch.common_suffix(["a-X-Y", "bb-X-Y"]), "-X-Y")
|
||||||
|
self.assertEqual(fetch.common_suffix(["word", "sword"]), "")
|
||||||
|
self.assertEqual(fetch.common_suffix(["only-one"]), "")
|
||||||
|
|
||||||
|
|
||||||
|
class Rdp(unittest.TestCase):
|
||||||
|
def test_sanitize(self):
|
||||||
|
text = fetch.decode_rdp(fake_rdweb.rdp_file("winword-x", "Word", "RemoteApp"))
|
||||||
|
out = fetch.sanitize_rdp(text)
|
||||||
|
self.assertIn("promptcredentialonce:i:1\r\n", out)
|
||||||
|
self.assertIn("prompt for credentials on client:i:0\r\n", out)
|
||||||
|
for gone in ("drivestoredirect", "devicestoredirect", "camerastoredirect"):
|
||||||
|
self.assertNotIn(gone, out)
|
||||||
|
self.assertIn("remoteapplicationprogram:s:||winword\r\n", out)
|
||||||
|
|
||||||
|
def test_encodings(self):
|
||||||
|
line = "full address:s:host\r\n"
|
||||||
|
for data in (line.encode("utf-16"), line.encode("utf-16-le"), line.encode("utf-8-sig"), line.encode()):
|
||||||
|
self.assertEqual(fetch.decode_rdp(data), line)
|
||||||
|
|
||||||
|
def test_append(self):
|
||||||
|
self.assertEqual(fetch.rdp_set("a:s:b\r\n", "promptcredentialonce", "i", "1"),
|
||||||
|
"a:s:b\r\npromptcredentialonce:i:1\r\n")
|
||||||
|
|
||||||
|
|
||||||
|
class Icons(unittest.TestCase):
|
||||||
|
def test_32bpp(self):
|
||||||
|
width, height, raw = png_info(fetch.ico_to_png(fake_rdweb.ico_32bpp(48, (30, 110, 220, 128))))
|
||||||
|
self.assertEqual((width, height), (48, 48))
|
||||||
|
self.assertEqual(raw[1:5], bytes((30, 110, 220, 128)))
|
||||||
|
|
||||||
|
def test_24bpp_with_mask(self):
|
||||||
|
size = 16
|
||||||
|
header = struct.pack("<IiiHHIIiiII", 40, size, size * 2, 1, 24, 0, 0, 0, 0, 0, 0)
|
||||||
|
stride = ((size * 24 + 31) // 32) * 4
|
||||||
|
pixels = (bytes((0, 0, 255)) * size + b"\x00" * (stride - size * 3)) * size
|
||||||
|
# The top row of the image (the last one in the file) is transparent.
|
||||||
|
mask = b"\x00\x00\x00\x00" * (size - 1) + b"\xff\xff\x00\x00"
|
||||||
|
dib = header + pixels + mask
|
||||||
|
ico = (struct.pack("<HHH", 0, 1, 1)
|
||||||
|
+ struct.pack("<BBBBHHII", size, size, 0, 0, 1, 24, len(dib), 22) + dib)
|
||||||
|
width, height, raw = png_info(fetch.ico_to_png(ico))
|
||||||
|
self.assertEqual((width, height), (16, 16))
|
||||||
|
self.assertEqual(raw[1:5], bytes((255, 0, 0, 0)))
|
||||||
|
row = 1 + width * 4
|
||||||
|
self.assertEqual(raw[row + 1:row + 5], bytes((255, 0, 0, 255)))
|
||||||
|
|
||||||
|
def test_png_frame_and_biggest(self):
|
||||||
|
small = fake_rdweb.ico_32bpp(16)
|
||||||
|
png = fetch.png_encode(2, 1, [bytes((1, 2, 3, 4, 5, 6, 7, 8))])
|
||||||
|
# Two entries: a 16 px bitmap and a 256 px PNG (width 0 means 256).
|
||||||
|
dib = small[22:]
|
||||||
|
ico = (struct.pack("<HHH", 0, 1, 2)
|
||||||
|
+ struct.pack("<BBBBHHII", 16, 16, 0, 0, 1, 32, len(dib), 38)
|
||||||
|
+ struct.pack("<BBBBHHII", 0, 0, 0, 0, 1, 32, len(png), 38 + len(dib)) + dib + png)
|
||||||
|
self.assertEqual(fetch.ico_to_png(ico), png)
|
||||||
|
|
||||||
|
def test_garbage(self):
|
||||||
|
self.assertIsNone(fetch.ico_to_png(b"not an icon"))
|
||||||
|
self.assertIsNone(fetch.ico_to_png(b"\x00\x00\x01\x00\x01\x00" + b"\x00" * 16))
|
||||||
|
|
||||||
|
|
||||||
|
class Subscribe(unittest.TestCase):
|
||||||
|
"""rdfeed-fetch against the fake server, as rdfeed runs it."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.server = fake_rdweb.serve()
|
||||||
|
cls.url = cls.server.base + "/RDWeb/Feed/webfeed.aspx"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
cls.server.shutdown()
|
||||||
|
|
||||||
|
def run_fetch(self, password, url=None, user="jdoe", domain=None):
|
||||||
|
out = tempfile.mkdtemp()
|
||||||
|
args = [sys.executable, FETCH, "--url", url or self.url, "--user", user, "--out", out, "--timeout", "5"]
|
||||||
|
if domain is not None:
|
||||||
|
args += ["--domain", domain]
|
||||||
|
proc = subprocess.run(args, input=password + "\n", capture_output=True, text=True, timeout=60)
|
||||||
|
return proc, out
|
||||||
|
|
||||||
|
def test_ok(self):
|
||||||
|
proc, out = self.run_fetch(fake_rdweb.PASSWORD)
|
||||||
|
self.assertEqual(proc.returncode, 0, proc.stderr)
|
||||||
|
lines = [line.split("\t") for line in proc.stdout.splitlines()]
|
||||||
|
self.assertIn(["workspace", "Contoso Apps"], lines)
|
||||||
|
self.assertIn(["domain", "CONTOSO"], lines)
|
||||||
|
self.assertIn(["app", "powerpnt", "RemoteApp", "PowerPoint"], lines)
|
||||||
|
self.assertIn(["app", "desktop", "Desktop", "Contoso Desktop"], lines)
|
||||||
|
with open(os.path.join(out, "apps.tsv")) as f:
|
||||||
|
rows = [line.split("\t") for line in f.read().splitlines()]
|
||||||
|
self.assertEqual(len(rows), 4)
|
||||||
|
types = {row[0]: row[3] for row in rows}
|
||||||
|
self.assertTrue(types["winword"].startswith("docx docm doc "))
|
||||||
|
self.assertEqual(types["excel"], "xlsx csv")
|
||||||
|
self.assertEqual(types["desktop"], "")
|
||||||
|
with open(os.path.join(out, "rdp", "winword.rdp")) as f:
|
||||||
|
rdp = f.read()
|
||||||
|
self.assertIn("promptcredentialonce:i:1", rdp)
|
||||||
|
self.assertNotIn("drivestoredirect", rdp)
|
||||||
|
with open(os.path.join(out, "icons", "excel.png"), "rb") as f:
|
||||||
|
self.assertEqual(png_info(f.read())[:2], (48, 48))
|
||||||
|
|
||||||
|
def test_domain_given(self):
|
||||||
|
proc, _ = self.run_fetch(fake_rdweb.PASSWORD, domain="contoso")
|
||||||
|
self.assertEqual(proc.returncode, 0, proc.stderr)
|
||||||
|
|
||||||
|
def test_wrong_password(self):
|
||||||
|
proc, _ = self.run_fetch("nope")
|
||||||
|
self.assertEqual(proc.returncode, fetch.EXIT_AUTH)
|
||||||
|
|
||||||
|
def test_no_feed(self):
|
||||||
|
proc, _ = self.run_fetch(fake_rdweb.PASSWORD, url=self.server.base + "/somewhere/else")
|
||||||
|
self.assertEqual(proc.returncode, fetch.EXIT_NOFEED)
|
||||||
|
|
||||||
|
def test_unreachable(self):
|
||||||
|
proc, _ = self.run_fetch(fake_rdweb.PASSWORD, url="http://127.0.0.1:9/RDWeb/Feed/webfeed.aspx")
|
||||||
|
self.assertEqual(proc.returncode, fetch.EXIT_NETWORK)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# rdfeed-hook.so: the wait before a new gateway tunnel, and GFX kept off.
|
||||||
|
# Connections go to port 443 of this computer, nothing has to answer there.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2026 Felitendo
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." || exit 1
|
||||||
|
|
||||||
|
hook="$(readlink -f "${RDFEED_HOOK:-build/rdfeed-hook.so}")"
|
||||||
|
[[ -f $hook ]] || { echo "no hook at $hook, run make first" >&2; exit 1; }
|
||||||
|
failed=0
|
||||||
|
|
||||||
|
check() {
|
||||||
|
if [[ $2 == "$3" ]]; then
|
||||||
|
printf 'ok %s\n' "$1"
|
||||||
|
else
|
||||||
|
printf 'FAIL %s: got "%s", want "%s"\n' "$1" "$2" "$3"
|
||||||
|
failed=1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# timings <wait mode> <pause>
|
||||||
|
# One connection, the pause, then two right after each other. Prints how long
|
||||||
|
# each one waited, in whole seconds, and whether the hook said so.
|
||||||
|
timings() {
|
||||||
|
LD_PRELOAD="$hook" RDFEED_GW_DELAY=1 RDFEED_GW_WAIT="$1" python3 - "$2" 2>&1 <<- 'EOF'
|
||||||
|
import socket, sys, time
|
||||||
|
def attempt():
|
||||||
|
start = time.monotonic()
|
||||||
|
s = socket.socket()
|
||||||
|
try:
|
||||||
|
s.connect(("127.0.0.1", 443))
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
s.close()
|
||||||
|
return round(time.monotonic() - start)
|
||||||
|
first = attempt()
|
||||||
|
time.sleep(float(sys.argv[1]))
|
||||||
|
second = attempt()
|
||||||
|
third = attempt()
|
||||||
|
print(first, second, third)
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
check "auto waits after a long first sign-in" "$(timings auto 3 | tr '\n' ' ')" "[rdfeed] gateway-delay 1 0 1 0 "
|
||||||
|
check "auto does not wait after a quick one" "$(timings auto 2.1)" "0 0 0"
|
||||||
|
check "always waits" "$(timings always 2.1 | tail -n1)" "0 1 0"
|
||||||
|
check "never waits not" "$(timings never 3)" "0 0 0"
|
||||||
|
check "the same tunnel does not wait" "$(timings always 0.5)" "0 0 0"
|
||||||
|
|
||||||
|
# GFX: what FreeRDP's own command line parser makes of it, with and without.
|
||||||
|
gfx() {
|
||||||
|
env "$@" python3 - <<- 'EOF'
|
||||||
|
import ctypes
|
||||||
|
try:
|
||||||
|
core = ctypes.CDLL("libfreerdp3.so.3", mode=ctypes.RTLD_GLOBAL)
|
||||||
|
client = ctypes.CDLL("libfreerdp-client3.so.3", mode=ctypes.RTLD_GLOBAL)
|
||||||
|
except OSError:
|
||||||
|
print("skip")
|
||||||
|
raise SystemExit
|
||||||
|
core.freerdp_settings_new.restype = ctypes.c_void_p
|
||||||
|
core.freerdp_settings_new.argtypes = [ctypes.c_uint32]
|
||||||
|
core.freerdp_settings_get_key_for_name.restype = ctypes.c_ssize_t
|
||||||
|
core.freerdp_settings_get_key_for_name.argtypes = [ctypes.c_char_p]
|
||||||
|
core.freerdp_settings_get_bool.argtypes = [ctypes.c_void_p, ctypes.c_size_t]
|
||||||
|
client.freerdp_client_settings_parse_command_line.argtypes = [
|
||||||
|
ctypes.c_void_p, ctypes.c_int, ctypes.POINTER(ctypes.c_char_p), ctypes.c_int]
|
||||||
|
settings = core.freerdp_settings_new(0)
|
||||||
|
argv = [b"xfreerdp3", b"/v:host.invalid", b"/gfx"]
|
||||||
|
client.freerdp_client_settings_parse_command_line(
|
||||||
|
settings, len(argv), (ctypes.c_char_p * len(argv))(*argv), 0)
|
||||||
|
key = core.freerdp_settings_get_key_for_name(b"FreeRDP_SupportGraphicsPipeline")
|
||||||
|
print("on" if core.freerdp_settings_get_bool(settings, key) else "off")
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
with="$(gfx LD_PRELOAD="$hook" RDFEED_NO_GFX=1 WLOG_LEVEL=OFF)"
|
||||||
|
if [[ $with == skip ]]; then
|
||||||
|
echo "skip GFX: FreeRDP 3 is not installed"
|
||||||
|
else
|
||||||
|
check "GFX stays off with RDFEED_NO_GFX" "$with" "off"
|
||||||
|
check "GFX is left alone without it" "$(gfx LD_PRELOAD="$hook" WLOG_LEVEL=OFF)" "on"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit "$failed"
|
||||||
Reference in new issue
Block a user