Fix Dropdown issue
This commit is contained in:
136
AGENTS.md
136
AGENTS.md
@@ -26,12 +26,28 @@ live in the daemon, not the GUI. Code is split across `src/` (GUI), `shared/`
|
|||||||
|
|
||||||
## i18n — easy to get wrong
|
## i18n — easy to get wrong
|
||||||
- English is the source language (the `msgid`) **and** also has an explicit
|
- English is the source language (the `msgid`) **and** also has an explicit
|
||||||
identity catalog `po/en.po`. Languages: `de`, `en`, `es`, `fr`.
|
identity catalog `po/en.po`. Languages: `de`, `en`, `es`, `fr`. The workflow,
|
||||||
- `make translations` does **not** regenerate the `.pot`. When you add or change
|
the rules behind it and adding a language are documented in `po/README.md`; the
|
||||||
a UI string, edit `po/gkeymapper.pot` and `po/*.po` by hand, then run
|
commands are `scripts/i18n.sh` (`make pot`, `make i18n-check`,
|
||||||
`make translations`; validate with `msgfmt --check -o /dev/null po/<lang>.po`.
|
`make i18n-status`).
|
||||||
- Never edit the generated `.mo` files under `locale/` (gitignored). Delete any
|
- `make translations` does **not** regenerate the `.pot` (a pot written by a build
|
||||||
`po/*.po~` backups before committing.
|
is a pot nobody reviews). When you add or change a UI string: `make pot`, then
|
||||||
|
`scripts/i18n.sh update` to merge it into the catalogs, then fill in the msgstrs
|
||||||
|
in `po/*.po` — and in `po/en.po`, whose msgstr repeats the msgid. `_()` is
|
||||||
|
translated where it is used, `N_()` is only collected (the help paragraphs in
|
||||||
|
`src/help.cpp`); the script always passes both keywords to `xgettext`.
|
||||||
|
- The catalogs carry **no** `#: file:line` comments; the locations live in the pot
|
||||||
|
only (`update` merges with `msgmerge --no-location`) and
|
||||||
|
`scripts/i18n.sh todo <lang>` shows them from there. `make i18n-check` fails
|
||||||
|
when the pot is behind the sources, when a catalog misses a message or a
|
||||||
|
translation, or when `po/*.po` and `kLanguageCodes[]` (`src/optionsdialog.cpp`)
|
||||||
|
disagree — a catalog nobody can select is dead weight.
|
||||||
|
- The daemon is **not** translated (no `setlocale`/`bindtextdomain`, only the GUI
|
||||||
|
sets gettext up in `main()`); `i18n-check` reports a `_()` marker under
|
||||||
|
`daemon/`.
|
||||||
|
- Never edit the generated `.mo` files under `locale/` (gitignored). A manual
|
||||||
|
`msgmerge --update` leaves `po/*.po~` backups — the script deletes them, by hand
|
||||||
|
delete them before committing.
|
||||||
- All documentation and source comments are English. German appears only inside
|
- All documentation and source comments are English. German appears only inside
|
||||||
`po/de.po`.
|
`po/de.po`.
|
||||||
|
|
||||||
@@ -43,8 +59,8 @@ live in the daemon, not the GUI. Code is split across `src/` (GUI), `shared/`
|
|||||||
app id, the version and the D-Bus names both sides use. The GUI side is
|
app id, the version and the D-Bus names both sides use. The GUI side is
|
||||||
`main.cpp` (window, actions) plus `keyrow.h`, `mappings`, `capturedialog`,
|
`main.cpp` (window, actions) plus `keyrow.h`, `mappings`, `capturedialog`,
|
||||||
`daemonclient`, `singleinstance`, `trayicon` (StatusNotifierItem + dbusmenu),
|
`daemonclient`, `singleinstance`, `trayicon` (StatusNotifierItem + dbusmenu),
|
||||||
`settings` (`gui.conf`), `autostart` (the session's `.desktop` entry), `help`
|
`settings` (`gui.conf`), `autostart` (the session's `.desktop` entry), `help`,
|
||||||
and `optionsdialog`.
|
`optionsdialog` and `popovers` (the popup input grab, see below).
|
||||||
- The Makefile globs sources: `make` builds the GUI (`src/*.cpp` + `shared/*.cpp`
|
- The Makefile globs sources: `make` builds the GUI (`src/*.cpp` + `shared/*.cpp`
|
||||||
+ the generated `build/gresources.c`, see Assets) **and** the daemon
|
+ the generated `build/gresources.c`, see Assets) **and** the daemon
|
||||||
(`daemon/*.cpp` + `shared/*.cpp`, needs `libusb-1.0-0-dev`,
|
(`daemon/*.cpp` + `shared/*.cpp`, needs `libusb-1.0-0-dev`,
|
||||||
@@ -120,9 +136,18 @@ live in the daemon, not the GUI. Code is split across `src/` (GUI), `shared/`
|
|||||||
with `SIGTERM` after checking that the bus name really belongs to a
|
with `SIGTERM` after checking that the bus name really belongs to a
|
||||||
`gkeymapper` process. Only then does the new process become the primary,
|
`gkeymapper` process. Only then does the new process become the primary,
|
||||||
because otherwise `GtkApplication` would hand over to the old process and its
|
because otherwise `GtkApplication` would hand over to the old process and its
|
||||||
window — possibly one from an older build — would stay on screen. Row edits
|
window — possibly one from an older build — would stay on screen.
|
||||||
that were not saved are gone with the replaced window; there is no implicit
|
- The takeover also carries the rows over, so an edit that was typed but never
|
||||||
save.
|
saved is not lost with the replaced window: before the quit,
|
||||||
|
`replace_running_instance()` asks for them (`Window.GetRows` at
|
||||||
|
`ipc::kWindowObjectPath`) and `build_ui()` starts the new window from that text
|
||||||
|
instead of the file. The answering side is `serve_rows_handover()` — called from
|
||||||
|
`on_activate` **after** `build_ui()`, since that is what fills the rows — with
|
||||||
|
`mappings_text(rows)`, i.e. the mappings file format, so both ends use
|
||||||
|
`src/mappings`. An instance that does not answer (an older build) leaves the new
|
||||||
|
window with the file; asking and serving are best effort and never fatal. The
|
||||||
|
file itself is untouched until the user presses `Save mappings` — the handover
|
||||||
|
carries row state, it does not save.
|
||||||
- `on_activate` presents the window that is already there
|
- `on_activate` presents the window that is already there
|
||||||
(`gtk_application_get_windows()`), it does not build a second one: a remote
|
(`gtk_application_get_windows()`), it does not build a second one: a remote
|
||||||
activation — an older build starting next to a new one — would otherwise add a
|
activation — an older build starting next to a new one — would otherwise add a
|
||||||
@@ -168,9 +193,17 @@ live in the daemon, not the GUI. Code is split across `src/` (GUI), `shared/`
|
|||||||
deleting it. The `Exec` line is the binary that is running now
|
deleting it. The `Exec` line is the binary that is running now
|
||||||
(`g_file_read_link("/proc/self/exe")`, quoted, falls back to the name from
|
(`g_file_read_link("/proc/self/exe")`, quoted, falls back to the name from
|
||||||
`PATH`), so an enabled switch starts the build the user was looking at, and the
|
`PATH`), so an enabled switch starts the build the user was looking at, and the
|
||||||
`Comment` reuses the About text (no second sentence for translators). Only the
|
`Comment` reuses the About text (no second sentence for translators). That
|
||||||
**window** is started; the daemon is a systemd user service and stays untouched
|
`Exec` line also carries `--start-hidden` (`kStartHiddenArg`, taken off the
|
||||||
— the GUI is a convenience, the daemon is the part that has to run.
|
command line in `main()`), so the window is built but stays in the tray: a login
|
||||||
|
does not put it in front of the user, while a start by hand (no flag) always
|
||||||
|
shows it. Keeping it hidden is only safe while the panel really hosts the icon,
|
||||||
|
and only the panel can answer that, asynchronously — so
|
||||||
|
`on_start_hidden_timeout()` shows the window after `kStartHiddenGraceMs` if the
|
||||||
|
icon never turned up. A rebuild (language switch) comes up the way the window
|
||||||
|
was left: `build_ui()` takes the visibility as an argument. Only the **window**
|
||||||
|
is started; the daemon is a systemd user service and stays untouched — the GUI
|
||||||
|
is a convenience, the daemon is the part that has to run.
|
||||||
- `Minimize to tray` hides the window on close (`"close-request"` handler) and on
|
- `Minimize to tray` hides the window on close (`"close-request"` handler) and on
|
||||||
minimize. GTK4 has **no** minimize signal and `GdkToplevel` exposes no
|
minimize. GTK4 has **no** minimize signal and `GdkToplevel` exposes no
|
||||||
properties, so `gdk_toplevel_get_state()` is polled every 250 ms while the
|
properties, so `gdk_toplevel_get_state()` is polled every 250 ms while the
|
||||||
@@ -222,6 +255,30 @@ live in the daemon, not the GUI. Code is split across `src/` (GUI), `shared/`
|
|||||||
`GtkScrolledWindow` (one collapsed the rows earlier). Keep the fixed widths
|
`GtkScrolledWindow` (one collapsed the rows earlier). Keep the fixed widths
|
||||||
(Type dropdown 150 px, target entry 440 px min, action button 180 px) so rows
|
(Type dropdown 150 px, target entry 440 px min, action button 180 px) so rows
|
||||||
do not jump when the language changes.
|
do not jump when the language changes.
|
||||||
|
- Both dropdowns (Type in the main window, Language in the options window) give
|
||||||
|
every entry a **trailing space**, added where the model is built — the msgids
|
||||||
|
stay the bare words. `GtkDropDown` appends the check mark of the selected entry
|
||||||
|
directly behind the text (`setup_item()` in `gtkdropdown.c`), and without that
|
||||||
|
space it touches the last letter. The selection is read back by index, never by
|
||||||
|
the string, so the space is harmless everywhere else.
|
||||||
|
- `src/popovers.{h,cpp}` takes the input grab off the popovers of a window (the
|
||||||
|
dropdowns' popup lists and the header bar menu) and closes them itself: on a
|
||||||
|
click in the window outside the popup, on Escape, and when the window loses the
|
||||||
|
focus. GTK 4.6 grabs the pointer for an open popover, an X11 grab swallows the
|
||||||
|
clicks of **every** other client, and this GTK does not close the popup on a
|
||||||
|
click outside the application either — with one dropdown open nothing else
|
||||||
|
could be clicked any more, not another program and not the desktop, and the
|
||||||
|
popup stayed open until Escape (measured with a plain GTK4 probe: the click on
|
||||||
|
the bare desktop never reached it and `_NET_ACTIVE_WINDOW` did not change; a
|
||||||
|
click inside the app's own window did close the popup). GTK3 replayed such a
|
||||||
|
click to the window under the pointer, GTK4 dropped that. `autohide` is what
|
||||||
|
installs the grab, so switching it off hands the clicks back to the desktop;
|
||||||
|
the dismissal is then done by hand, which is the rest of the module. It runs
|
||||||
|
once per toplevel window (`build_ui`, `OptionsDialog::show`), notices popovers
|
||||||
|
GTK creates later on the press that opens them, and leaves the press on the
|
||||||
|
popup itself alone — that one is an entry choice and must reach the entry
|
||||||
|
(recognised by its own surface, not by its position, because GTK reports it in
|
||||||
|
the popup's coordinates).
|
||||||
- Because it is pure GTK4 it follows the system theme (Mint-Y ships a
|
- Because it is pure GTK4 it follows the system theme (Mint-Y ships a
|
||||||
`gtk-4.0` theme). Do **not** add the `suggested-action` CSS class — Mint-Y
|
`gtk-4.0` theme). Do **not** add the `suggested-action` CSS class — Mint-Y
|
||||||
tints it green and it clashes with the rest of the layout.
|
tints it green and it clashes with the rest of the layout.
|
||||||
@@ -415,7 +472,11 @@ replaced process exits `0` when it was closed through the D-Bus `quit` action, o
|
|||||||
instance from an older commit (`git worktree add /tmp/legacy <commit>`), start it
|
instance from an older commit (`git worktree add /tmp/legacy <commit>`), start it
|
||||||
and then start the current build next to it. Starting the GUI twice also covers
|
and then start the current build next to it. Starting the GUI twice also covers
|
||||||
the `activate` guard: without it the second start adds a second window to the
|
the `activate` guard: without it the second start adds a second window to the
|
||||||
running process.
|
running process. The rows come along (see the takeover notes above), which is
|
||||||
|
checkable the same way: type into a row of the first instance without saving,
|
||||||
|
start the second one and read its rows back (next bullet) — they show the edit,
|
||||||
|
while the mappings file still has the old content. An instance from an older
|
||||||
|
commit cannot hand rows over, so there the new window shows the file.
|
||||||
|
|
||||||
The footer's daemon line is a plain `GtkLabel`, so its text can be read back over
|
The footer's daemon line is a plain `GtkLabel`, so its text can be read back over
|
||||||
AT-SPI: `<app> → frame → panel → label` whose name starts with the translated
|
AT-SPI: `<app> → frame → panel → label` whose name starts with the translated
|
||||||
@@ -433,7 +494,12 @@ is up. Three notes for tests:
|
|||||||
`import -window $(wmctrl -l | grep 'gKeyMapper – Logitech' | awk '{print $1}')
|
`import -window $(wmctrl -l | grep 'gKeyMapper – Logitech' | awk '{print $1}')
|
||||||
/tmp/gui.png`. The footer label works over AT-SPI (its accessible *name* is the
|
/tmp/gui.png`. The footer label works over AT-SPI (its accessible *name* is the
|
||||||
text), but a screenshot of the cropped footer is easier and shows what the user
|
text), but a screenshot of the cropped footer is easier and shows what the user
|
||||||
sees.
|
sees. Easier than a screenshot for the rows themselves: the running instance
|
||||||
|
answers `gdbus call --session --dest dev.rager.gkeymapper --object-path
|
||||||
|
/dev/rager/gkeymapper/Window --method dev.rager.gkeymapper.Window.GetRows` with
|
||||||
|
exactly the mappings text its rows hold (`G1=command:/bin/true…`). It is the
|
||||||
|
same channel the takeover uses, so it also tells whether an edit is still
|
||||||
|
unsaved: compare it with the file.
|
||||||
- `XTest`-synthesized clicks need the window mapped and focused; the `Save`
|
- `XTest`-synthesized clicks need the window mapped and focused; the `Save`
|
||||||
button is `push button` + name `Mappings speichern` in the German locale.
|
button is `push button` + name `Mappings speichern` in the German locale.
|
||||||
|
|
||||||
@@ -447,12 +513,24 @@ The tray icon, the help and the options window need neither hardware nor a click
|
|||||||
pid, two registrations) means the "register once" rule broke.
|
pid, two registrations) means the "register once" rule broke.
|
||||||
- Its menu is readable without the panel:
|
- Its menu is readable without the panel:
|
||||||
`gdbus call --session --dest org.kde.StatusNotifierItem-<pid>-1 --object-path
|
`gdbus call --session --dest org.kde.StatusNotifierItem-<pid>-1 --object-path
|
||||||
/MenuBar --method com.canonical.dbusmenu.GetLayout 0 -1` returns the root node
|
/MenuBar --method com.canonical.dbusmenu.GetLayout -- 0 -1 "[]"` returns the
|
||||||
with the two children. Drive a click the way the panel does — **batched**, as
|
root node with the two children (the `--` keeps gdbus from reading the `-1` as
|
||||||
`EventGroup`, because that is the path a real click takes:
|
an option, and the empty array is the property-name list `GetLayout` expects).
|
||||||
|
Drive a click the way the panel does — **batched**, as `EventGroup`, because
|
||||||
|
that is the path a real click takes:
|
||||||
`gdbus call … --method com.canonical.dbusmenu.EventGroup
|
`gdbus call … --method com.canonical.dbusmenu.EventGroup
|
||||||
"[(1, 'clicked', <'', >, 0)]"` shows the window, id `2` quits the program.
|
"[(1, 'clicked', <''>, 0)]"` shows the window, id `2` quits the program (the
|
||||||
A single `Event` call works too but is not what the watcher sends.
|
empty string is the variant `v` of the event; `<''>` is the only spelling gdbus
|
||||||
|
accepts). A single `Event` call works too but is not what the watcher sends.
|
||||||
|
- A start with `--start-hidden` (what the autostart entry passes) is checkable the
|
||||||
|
same way: no window in `wmctrl -lp` while the item above is registered and the
|
||||||
|
process stays, and the menu's `Show` brings the window up. Without a tray watcher
|
||||||
|
the window has to appear by itself after ~5 s — on a private bus that is awkward
|
||||||
|
to see, because a fresh `dbus-daemon` makes GTK's startup wait on gvfs/portal
|
||||||
|
services (`GIO_USE_VFS=local` unblocks it, and it can still take ~20 s), so
|
||||||
|
`on_activate` runs much later than on the session bus. Do not read "no window" as
|
||||||
|
the flag working until the tray item or the window object
|
||||||
|
(`Window.GetRows`) proves the app got that far.
|
||||||
- The watcher is fragile by construction: it walks the layout it mirrors, so a
|
- The watcher is fragile by construction: it walks the layout it mirrors, so a
|
||||||
malformed node (bare ids instead of full nodes) aborts it. After a change to
|
malformed node (bare ids instead of full nodes) aborts it. After a change to
|
||||||
the layout, check it survived (`pgrep -a xapp-sn-watcher`) and that no new
|
the layout, check it survived (`pgrep -a xapp-sn-watcher`) and that no new
|
||||||
@@ -482,6 +560,22 @@ The tray icon, the help and the options window need neither hardware nor a click
|
|||||||
One pitfall while clicking: the options window is transient for the main
|
One pitfall while clicking: the options window is transient for the main
|
||||||
window, so it always stays above it — clicks meant for the main window hit the
|
window, so it always stays above it — clicks meant for the main window hit the
|
||||||
options window unless it is closed first.
|
options window unless it is closed first.
|
||||||
|
- The popups (`src/popovers.cpp`) are checkable without hardware, and a click
|
||||||
|
test has to know two things: a popup is an override-redirect window that
|
||||||
|
disappears from `xwininfo`'s map state (or a `viewable.py`-style walk) but never
|
||||||
|
appears in `wmctrl -l`, and a click on the dropdown that is *already* open
|
||||||
|
closes it again — so "click the dropdown" does not mean "the popup is open",
|
||||||
|
always read the map state. What to check: with a type dropdown open, a click on
|
||||||
|
a *different* window must arrive there (`_NET_ACTIVE_WINDOW` changes to that
|
||||||
|
client, which proves the press was delivered and not swallowed) and the popup
|
||||||
|
must be gone; the same for a click on the bare desktop; a click in the window
|
||||||
|
outside the popup closes it without changing the focus; Escape closes it; a
|
||||||
|
click on an entry closes it and changes the row (`Window.GetRows`, e.g.
|
||||||
|
`G1=key:…` becomes `G1=command:…`); a click on the header bar's menu button
|
||||||
|
opens the menu and behaves like the dropdown. Every synthetic click must be
|
||||||
|
guarded by the id of the window that is expected to be topmost — the popup is
|
||||||
|
not in `_NET_CLIENT_LIST_STACKING`, so a click aimed at the popup has to be
|
||||||
|
checked against the window *below* it.
|
||||||
- The autostart switch needs no click to read and one click to write, because the
|
- The autostart switch needs no click to read and one click to write, because the
|
||||||
`.desktop` entry *is* the state: put `$HOME/.config/autostart/gkeymapper.desktop`
|
`.desktop` entry *is* the state: put `$HOME/.config/autostart/gkeymapper.desktop`
|
||||||
there (or delete it), close the options window (`wmctrl -i -c <id>`) and
|
there (or delete it), close the options window (`wmctrl -i -c <id>`) and
|
||||||
|
|||||||
25
Makefile
25
Makefile
@@ -1,5 +1,6 @@
|
|||||||
# gKeyMapper - Build
|
# gKeyMapper - Build
|
||||||
# Dependencies: g++, pkg-config, msgfmt, libgtk-4-dev, libusb-1.0-0-dev, libxkbcommon-dev
|
# Dependencies: g++, pkg-config, gettext (msgfmt, for the translations),
|
||||||
|
# libgtk-4-dev, libusb-1.0-0-dev, libxkbcommon-dev
|
||||||
# Installation: sudo apt install build-essential gettext libgtk-4-dev libusb-1.0-0-dev libxkbcommon-dev
|
# Installation: sudo apt install build-essential gettext libgtk-4-dev libusb-1.0-0-dev libxkbcommon-dev
|
||||||
|
|
||||||
CXX ?= g++
|
CXX ?= g++
|
||||||
@@ -42,12 +43,15 @@ DBUS_DIR := $(PREFIX)/share/dbus-1/services
|
|||||||
CXXFLAGS += -DPACKAGE=\"$(PACKAGE)\" -DLOCALEDIR=\"$(LOCALEDIR)\" -DASSETS_DIR=\"$(ASSETS_DIR)\"
|
CXXFLAGS += -DPACKAGE=\"$(PACKAGE)\" -DLOCALEDIR=\"$(LOCALEDIR)\" -DASSETS_DIR=\"$(ASSETS_DIR)\"
|
||||||
CXXFLAGS += -I$(SHARED_DIR)
|
CXXFLAGS += -I$(SHARED_DIR)
|
||||||
|
|
||||||
# Translations
|
# Translations. The .pot is generated from the sources (make pot) and the
|
||||||
|
# catalogs are merged against it (scripts/i18n.sh update); the rules are in
|
||||||
|
# po/README.md.
|
||||||
PO_FILES := $(wildcard po/*.po)
|
PO_FILES := $(wildcard po/*.po)
|
||||||
LANGS := $(patsubst po/%.po,%,$(PO_FILES))
|
LANGS := $(patsubst po/%.po,%,$(PO_FILES))
|
||||||
MO_FILES := $(patsubst %,locale/%/LC_MESSAGES/$(PACKAGE).mo,$(LANGS))
|
MO_FILES := $(patsubst %,locale/%/LC_MESSAGES/$(PACKAGE).mo,$(LANGS))
|
||||||
|
I18N := $(CURDIR)/scripts/i18n.sh
|
||||||
|
|
||||||
.PHONY: all clean run install translations daemon
|
.PHONY: all clean run install translations daemon pot i18n-check i18n-status
|
||||||
|
|
||||||
all: $(TARGET) translations $(DAEMON)
|
all: $(TARGET) translations $(DAEMON)
|
||||||
|
|
||||||
@@ -72,6 +76,21 @@ locale/%/LC_MESSAGES/$(PACKAGE).mo: po/%.po
|
|||||||
@mkdir -p $(@D)
|
@mkdir -p $(@D)
|
||||||
$(MSGFMT) -o $@ $<
|
$(MSGFMT) -o $@ $<
|
||||||
|
|
||||||
|
# Regenerate po/gkeymapper.pot from the sources. "make translations" does not: a
|
||||||
|
# pot that is written by a build is a pot that nobody has looked at, and the
|
||||||
|
# strings are reviewed by hand (po/README.md, "Adding or changing a string").
|
||||||
|
pot:
|
||||||
|
$(I18N) extract
|
||||||
|
|
||||||
|
# Everything that can be wrong with the translations at once: the pot against the
|
||||||
|
# sources, every catalog against the pot, and the language list of the GUI against
|
||||||
|
# po/*.po. Run it before committing a string change.
|
||||||
|
i18n-check:
|
||||||
|
$(I18N) check
|
||||||
|
|
||||||
|
i18n-status:
|
||||||
|
$(I18N) status
|
||||||
|
|
||||||
run: $(TARGET) translations
|
run: $(TARGET) translations
|
||||||
GKEYMAPPER_LOCALEDIR=$(CURDIR)/locale GKEYMAPPER_ASSETS_DIR=$(CURDIR)/assets ./$(TARGET)
|
GKEYMAPPER_LOCALEDIR=$(CURDIR)/locale GKEYMAPPER_ASSETS_DIR=$(CURDIR)/assets ./$(TARGET)
|
||||||
|
|
||||||
|
|||||||
37
README.md
37
README.md
@@ -75,6 +75,16 @@ window icon works without an installed `assets/` directory.
|
|||||||
make translations
|
make translations
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Adding or changing a translation, adding a language, and the checks around them
|
||||||
|
are described in [po/README.md](po/README.md). `scripts/i18n.sh` does the work and
|
||||||
|
is wired into the Makefile:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make pot # regenerate po/gkeymapper.pot from the sources
|
||||||
|
make i18n-check # pot, catalogs and the language list of the GUI
|
||||||
|
make i18n-status # per language: translated, fuzzy, untranslated
|
||||||
|
```
|
||||||
|
|
||||||
## Run
|
## Run
|
||||||
|
|
||||||
Normal:
|
Normal:
|
||||||
@@ -105,8 +115,10 @@ window rewrites the same content instead of emptying the file.
|
|||||||
Only one window is open at a time, and it is the one you just started: a new
|
Only one window is open at a time, and it is the one you just started: a new
|
||||||
start closes the instance that is already running — including one from an older
|
start closes the instance that is already running — including one from an older
|
||||||
build — and then shows its own window, so a stale window can never stay next to a
|
build — and then shows its own window, so a stale window can never stay next to a
|
||||||
fresh one. Row edits that were not saved are lost with the replaced window,
|
fresh one. The rows of the window it replaced come along, so an edit that was
|
||||||
because saving is always explicit (`Save mappings`).
|
typed but never saved is still there; an instance from an older build cannot hand
|
||||||
|
them over, and then the window shows the saved mappings. Saving itself stays
|
||||||
|
explicit either way: press `Save mappings`.
|
||||||
|
|
||||||
### Menu, tray icon and minimize to tray
|
### Menu, tray icon and minimize to tray
|
||||||
|
|
||||||
@@ -122,12 +134,15 @@ configuration...`, `Options`, `Help` (also `F1`), `About` and `Quit` (`Ctrl+Q`).
|
|||||||
(`System language`, or a fixed `de`/`en`/`es`/`fr`): it switches the window, the
|
(`System language`, or a fixed `de`/`en`/`es`/`fr`): it switches the window, the
|
||||||
menus and the tray menu right away — nothing to restart, and rows that were not
|
menus and the tray menu right away — nothing to restart, and rows that were not
|
||||||
saved yet are kept.
|
saved yet are kept.
|
||||||
* `Start with the session` opens the window when you log in. It writes the usual
|
* `Start with the session` starts the window minimized in the tray when you log
|
||||||
autostart entry (`~/.config/autostart/gkeymapper.desktop`, starting the binary
|
in: it writes the usual autostart entry
|
||||||
that is running) and removes it again when you switch it off; an entry that the
|
(`~/.config/autostart/gkeymapper.desktop`, starting the binary that is running
|
||||||
desktop's own startup tool has disabled counts as off, too. Like the window
|
with the `--start-hidden` argument) and removes it again when you switch it off;
|
||||||
itself this is a convenience: the G keys do not need it, only the daemon has to
|
an entry that the desktop's own startup tool has disabled counts as off, too. The
|
||||||
run (see `Running the daemon` below).
|
window then waits in the tray until its `Show`, and only a desktop that hosts that
|
||||||
|
icon can leave it hidden — one without a tray gets the window a few seconds after
|
||||||
|
login. Like the window itself this is a convenience: the G keys do not need it,
|
||||||
|
only the daemon has to run (see `Running the daemon` below).
|
||||||
|
|
||||||
`Minimize to tray` keeps gKeyMapper reachable from the panel: the window is
|
`Minimize to tray` keeps gKeyMapper reachable from the panel: the window is
|
||||||
hidden instead of closed when you close or minimize it, and the tray icon's menu
|
hidden instead of closed when you close or minimize it, and the tray icon's menu
|
||||||
@@ -340,7 +355,8 @@ src/keyrow.h # KeyRow (one per G key)
|
|||||||
src/mappings.{h,cpp} # GUI glue: config file <-> row widgets
|
src/mappings.{h,cpp} # GUI glue: config file <-> row widgets
|
||||||
src/capturedialog.{h,cpp} # key-capture dialog
|
src/capturedialog.{h,cpp} # key-capture dialog
|
||||||
src/daemonclient.{h,cpp} # D-Bus client for the daemon (status/reload)
|
src/daemonclient.{h,cpp} # D-Bus client for the daemon (status/reload)
|
||||||
src/singleinstance.{h,cpp} # one GUI only: a new start closes a running instance
|
src/singleinstance.{h,cpp} # one GUI only: a new start replaces a running one,
|
||||||
|
# taking the rows of its window along (unsaved edits)
|
||||||
src/trayicon.{h,cpp} # tray icon: StatusNotifierItem + dbusmenu over GIO
|
src/trayicon.{h,cpp} # tray icon: StatusNotifierItem + dbusmenu over GIO
|
||||||
src/settings.{h,cpp} # gui.conf (window preferences, e.g. minimize to tray)
|
src/settings.{h,cpp} # gui.conf (window preferences, e.g. minimize to tray)
|
||||||
src/help.{h,cpp} # help window
|
src/help.{h,cpp} # help window
|
||||||
@@ -361,7 +377,8 @@ data/gkeymapper-uinput.conf # modules-load.d fragment for uinput
|
|||||||
data/gkeymapper-daemon.service# systemd user unit (installed by make install)
|
data/gkeymapper-daemon.service# systemd user unit (installed by make install)
|
||||||
data/dev.rager.gkeymapper.Daemon.service # D-Bus activation (session bus)
|
data/dev.rager.gkeymapper.Daemon.service # D-Bus activation (session bus)
|
||||||
docs/daemon-plan.md # daemon plan (reference: g910-gkey-macro-support)
|
docs/daemon-plan.md # daemon plan (reference: g910-gkey-macro-support)
|
||||||
po/ # Translations (pot/po/mo targets)
|
po/ # Translations (pot/po/mo targets, po/README.md)
|
||||||
|
scripts/i18n.sh # extract/update/check/status/todo/new for the catalogs
|
||||||
Makefile # Build via pkg-config + gettext
|
Makefile # Build via pkg-config + gettext
|
||||||
.vscode/tasks.json # VS Code tasks for make
|
.vscode/tasks.json # VS Code tasks for make
|
||||||
README.md
|
README.md
|
||||||
|
|||||||
@@ -361,6 +361,12 @@ Start-up orders:
|
|||||||
comes from a build without the action — and `on_activate` re-presents the
|
comes from a build without the action — and `on_activate` re-presents the
|
||||||
window that is already there instead of building a second one. A stale window
|
window that is already there instead of building a second one. A stale window
|
||||||
therefore never stays next to a fresh one, and the daemon is not involved.
|
therefore never stays next to a fresh one, and the daemon is not involved.
|
||||||
|
Closing the old window would drop row edits that were never saved, so the
|
||||||
|
takeover asks it for its rows first (`GetRows` at `/dev/rager/gkeymapper/Window`,
|
||||||
|
see `shared/ipc_ids.h`) and the new `build_ui()` starts from that mappings text
|
||||||
|
instead of from `mappings.conf`. An instance that does not answer — an older
|
||||||
|
build — leaves the new window with the file, and the file stays untouched until
|
||||||
|
the user presses `Save mappings`.
|
||||||
- Menu, help and options: `Help` (also `F1`) opens a one-page explanation of the
|
- Menu, help and options: `Help` (also `F1`) opens a one-page explanation of the
|
||||||
rows, types, capture dialog, daemon status and tray; `Options` opens a small
|
rows, types, capture dialog, daemon status and tray; `Options` opens a small
|
||||||
non-modal window with the GUI's own switches (tray behaviour, language,
|
non-modal window with the GUI's own switches (tray behaviour, language,
|
||||||
|
|||||||
178
po/README.md
Normal file
178
po/README.md
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
# Translations (i18n)
|
||||||
|
|
||||||
|
Everything about the catalogs, in one place: which file is which, how a string gets
|
||||||
|
from the source code into a translated window, and how to add a language. The
|
||||||
|
commands live in `scripts/i18n.sh`, wired into the Makefile as `pot`,
|
||||||
|
`i18n-check` and `i18n-status`.
|
||||||
|
|
||||||
|
## The pieces
|
||||||
|
|
||||||
|
| file | what it is |
|
||||||
|
| --- | --- |
|
||||||
|
| `po/gkeymapper.pot` | the template: every translatable string in the sources, with the file and line it comes from. **Generated** by `make pot` — never translated, and its msgids are never edited by hand. |
|
||||||
|
| `po/<lang>.po` | one catalog per language. This is what a translator edits. |
|
||||||
|
| `locale/<lang>/LC_MESSAGES/gkeymapper.mo` | the compiled catalog the program reads at runtime. Generated by `make translations`, gitignored. **Never edit** — it is overwritten on every build. |
|
||||||
|
| `scripts/i18n.sh` | `extract`, `update`, `check`, `status`, `todo`, `new` (see below). |
|
||||||
|
|
||||||
|
Languages: `de`, `en`, `es`, `fr`. The set is `po/*.po` — the Makefile picks up a
|
||||||
|
new catalog by itself (`LANGS`), nothing has to be registered.
|
||||||
|
|
||||||
|
`en.po` is special: English is the source language, and its catalog repeats every
|
||||||
|
msgid as its msgstr (an *identity* catalog). It exists so that choosing
|
||||||
|
"English" works on a German desktop, where the session locale would otherwise
|
||||||
|
select the German catalog. `i18n-check` enforces the identity — a translation that
|
||||||
|
drifts in is reported.
|
||||||
|
|
||||||
|
## How a string reaches the window
|
||||||
|
|
||||||
|
1. `_("...")` marks it in `src/` (or `shared/`) — the two directories the GUI is
|
||||||
|
built from. `N_("...")` is for strings that are only *collected* when they are
|
||||||
|
not translated yet: `src/help.cpp` keeps its paragraphs in a static array and
|
||||||
|
translates them when the window is built (`_(text)`). Both keywords are in the
|
||||||
|
pot; a string marked with neither is invisible to translators.
|
||||||
|
2. `make pot` writes `po/gkeymapper.pot` from the sources.
|
||||||
|
3. `scripts/i18n.sh update` merges the pot into every catalog. New strings come in
|
||||||
|
with an empty msgstr (= untranslated), existing translations are kept.
|
||||||
|
4. Translating is editing `po/<lang>.po`: replace the msgstr, leave the msgid
|
||||||
|
alone. Any text editor, or `poedit` if you prefer one.
|
||||||
|
5. `make translations` compiles `locale/<lang>/LC_MESSAGES/gkeymapper.mo`.
|
||||||
|
6. The program reads the catalog whose language it was started with. The GUI picks
|
||||||
|
it up automatically once gettext is set up in `src/main.cpp`; *which* language is
|
||||||
|
used comes from the options window (stored in `~/.config/gKeyMapper/gui.conf`,
|
||||||
|
`language=de`), or from the session when that setting is empty. Internally
|
||||||
|
`apply_gui_language()` (`src/settings.cpp`) sets `LANGUAGE` and re-runs
|
||||||
|
`setlocale` — `LANGUAGE` wins over `LANG`/`LC_ALL` in gettext, which is what
|
||||||
|
makes switching without a restart and an explicit `en` on a German desktop work.
|
||||||
|
The catalogs are searched under `$GKEYMAPPER_LOCALEDIR`, falling back to the
|
||||||
|
compiled-in `LOCALEDIR`.
|
||||||
|
|
||||||
|
## The common case: a string is added or changed
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. in the code
|
||||||
|
_("New text") # or N_("...") for a deferred one
|
||||||
|
|
||||||
|
# 2. the template
|
||||||
|
make pot # prints what is new and what is gone
|
||||||
|
|
||||||
|
# 3. what is missing where
|
||||||
|
make i18n-check # names every catalog that lacks it
|
||||||
|
|
||||||
|
# 4. every catalog takes it in (empty msgstr)
|
||||||
|
scripts/i18n.sh update # or: scripts/i18n.sh update de
|
||||||
|
|
||||||
|
# 5. translate
|
||||||
|
scripts/i18n.sh todo de # untranslated + fuzzy, with the source location
|
||||||
|
$EDITOR po/de.po # and es, fr - and en, where the msgstr is the msgid
|
||||||
|
|
||||||
|
# 6. check and compile
|
||||||
|
make i18n-check # must print "everything in sync"
|
||||||
|
make translations
|
||||||
|
|
||||||
|
# 7. see it
|
||||||
|
GKEYMAPPER_LOCALEDIR=$PWD/locale LANGUAGE=de_DE.UTF-8 ./gkeymapper
|
||||||
|
```
|
||||||
|
|
||||||
|
Commit the source change, `po/gkeymapper.pot` and the catalogs **together**: a pot
|
||||||
|
without the catalogs is a half-done change, and `make i18n-check` says so.
|
||||||
|
|
||||||
|
A new string in one place and the same text in another is *one* message for
|
||||||
|
gettext — the pot lists both locations, and one msgstr covers both.
|
||||||
|
|
||||||
|
## Adding a language
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scripts/i18n.sh new it Italiano # po/it.po, header in the project's shape,
|
||||||
|
# every msgstr empty (the second word is
|
||||||
|
# Language-Team; it defaults to the code)
|
||||||
|
scripts/i18n.sh todo it # work through it
|
||||||
|
$EDITOR po/it.po
|
||||||
|
```
|
||||||
|
|
||||||
|
Then offer it in the GUI, or nobody can select it — `src/optionsdialog.cpp`:
|
||||||
|
|
||||||
|
* `kLanguageCodes[]` — the codes, in the same order as the labels ("", the empty
|
||||||
|
one, means "follow the session" and has to stay first);
|
||||||
|
* `languageOptions[]` — the labels. Use the **endonym** ("Italiano", not
|
||||||
|
"Italian"): the list reads the same in every language, so a user who lands in
|
||||||
|
the wrong language can still find their own.
|
||||||
|
|
||||||
|
`make i18n-check` checks exactly that pairing (a catalog that cannot be selected, a
|
||||||
|
code without a catalog, two arrays of different length). `make translations`
|
||||||
|
compiles the new catalog; `make run` and the test command above use the working
|
||||||
|
tree's `locale/`.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
| command | what it does |
|
||||||
|
| --- | --- |
|
||||||
|
| `scripts/i18n.sh extract` (`make pot`) | Regenerate `po/gkeymapper.pot` from the sources. Prints the messages that appeared and disappeared. Refuses to write an empty pot, so a broken gettext cannot empty the good one. |
|
||||||
|
| `scripts/i18n.sh update [lang...]` | Merge the pot into the catalogs, without source locations (see Conventions). Keeps translations, marks new strings untranslated. |
|
||||||
|
| `scripts/i18n.sh check` (`make i18n-check`) | Everything below. Exit code 1 when something is wrong, so it also works as a pre-commit step. |
|
||||||
|
| `scripts/i18n.sh status` (`make i18n-status`) | Per language: messages, translated, fuzzy, untranslated, and whether the compiled catalog is older than the po. |
|
||||||
|
| `scripts/i18n.sh todo <lang>` | The untranslated and fuzzy messages, with the source locations taken from the pot. The output is a valid po file, if you want to keep it. |
|
||||||
|
| `scripts/i18n.sh new <lang> [team]` | Create `po/<lang>.po` from the pot. `new en` writes the identity catalog. |
|
||||||
|
|
||||||
|
The gettext tools are taken from `PATH` and can be overridden like the Makefile's
|
||||||
|
variables, e.g. `XGETTEXT=/usr/bin/xgettext scripts/i18n.sh extract`.
|
||||||
|
|
||||||
|
## What `check` looks at
|
||||||
|
|
||||||
|
* the pot against the sources: same messages? A string added or changed without
|
||||||
|
`make pot` is the mistake this catches (a `#:` line number that moved is only
|
||||||
|
reported as a note — refresh it whenever it suits you);
|
||||||
|
* every catalog: it parses (`msgfmt -c`, which also compares the format
|
||||||
|
specifiers of msgid and msgstr), it defines the same messages as the pot, nothing
|
||||||
|
is untranslated or fuzzy, and its `Language:` header matches its file name;
|
||||||
|
* `po/en.po` is an identity catalog;
|
||||||
|
* `po/*.po` and the language list of the options window (`src/optionsdialog.cpp`)
|
||||||
|
agree;
|
||||||
|
* no strings are marked in `daemon/`: the daemon has no gettext setup, so `_()`
|
||||||
|
there would be translated into a catalog that is never read.
|
||||||
|
|
||||||
|
Notes that are *not* failures: catalogs that were not merged since the last
|
||||||
|
extract, and obsolete entries (`#~`, strings that are gone from the sources —
|
||||||
|
`msgattrib --no-obsolete po/<lang>.po > tmp && mv tmp po/<lang>.po` drops them).
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
* **No `#: file:line` comments in the catalogs.** The locations are noise for a
|
||||||
|
translator and churn whenever a line moves, so they live only in the pot
|
||||||
|
(`update` therefore uses `msgmerge --no-location`) and `todo` shows them from
|
||||||
|
there when they are needed. This is also why the catalogs are merged with the
|
||||||
|
script instead of a bare `msgmerge --update`, which would copy the locations in.
|
||||||
|
* **The build does not regenerate the pot** (`make translations` only runs
|
||||||
|
`msgfmt`). A template that is rewritten by every build is a template nobody
|
||||||
|
looks at; instead `make pot` is a deliberate step and `make i18n-check` fails
|
||||||
|
when it was forgotten.
|
||||||
|
* **Mnemonics stay mnemonics.** `_Cancel` uses the `_` to underline a letter;
|
||||||
|
`_Abbrechen` moves it to a letter that exists in that language. A translation
|
||||||
|
without the underscore loses the keyboard shortcut silently.
|
||||||
|
* **Format specifiers** (`%s`, `%d`) keep their number and order — `msgfmt -c`
|
||||||
|
catches a mismatch.
|
||||||
|
* **No plurals yet**: the program only uses `_()`, so no catalog carries a
|
||||||
|
`Plural-Forms` header. With the first `ngettext()` that header has to go into
|
||||||
|
every catalog (and `scripts/i18n.sh new` should write it).
|
||||||
|
* **HTML-ish markup** in a msgid (`<b>`, `<i>`) has to survive in the msgstr; the
|
||||||
|
help window renders it.
|
||||||
|
* German belongs in `po/de.po` only — sources, comments and documentation are
|
||||||
|
English.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
* *A window stays English while another window is translated* — the string is
|
||||||
|
missing from the catalog (or the catalog was not recompiled: `make translations`).
|
||||||
|
`scripts/i18n.sh todo <lang>` lists what is open.
|
||||||
|
* *Shortcut without an underline* — the mnemonic `_` is missing in the msgstr.
|
||||||
|
* *`po/*.po~` files appear* — that is `msgmerge`'s backup from a manual
|
||||||
|
`msgmerge --update`; the script deletes it. Never commit them.
|
||||||
|
* *A fuzzy entry is shown in English* — on purpose: fuzzy means "the text was
|
||||||
|
changed, this translation needs a look", and gettext falls back to the msgid
|
||||||
|
until it is confirmed (remove the `#, fuzzy` line).
|
||||||
|
* *"not merged against the current pot" in `check`* — run `scripts/i18n.sh update`;
|
||||||
|
until then the catalog header carries an older date than the pot.
|
||||||
|
* *Nothing is extracted at all* — `xgettext`'s built-in keyword list is not the
|
||||||
|
same everywhere, so the script always passes `--keyword=_ --keyword=N_`. If the
|
||||||
|
pot comes out empty, the script stops instead of replacing the pot.
|
||||||
|
|
||||||
|
The rest of the project's conventions (GUI, daemon, mappings) are in `AGENTS.md`.
|
||||||
317
po/de.po
317
po/de.po
@@ -6,7 +6,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: gkeymapper 0.1\n"
|
"Project-Id-Version: gkeymapper 0.1\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-18 03:08+0200\n"
|
"POT-Creation-Date: 2026-10-18 20:18+0200\n"
|
||||||
"PO-Revision-Date: 2026-10-18 03:08+0200\n"
|
"PO-Revision-Date: 2026-10-18 03:08+0200\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: German\n"
|
"Language-Team: German\n"
|
||||||
@@ -15,14 +15,8 @@ msgstr ""
|
|||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
msgid "Choose file"
|
msgid "G-Key mapper for the Logitech G910 (GTK4)."
|
||||||
msgstr "Datei auswählen"
|
msgstr "G-Key-Mapper für die Logitech G910 (GTK4)."
|
||||||
|
|
||||||
msgid "_Cancel"
|
|
||||||
msgstr "_Abbrechen"
|
|
||||||
|
|
||||||
msgid "_Open"
|
|
||||||
msgstr "_Öffnen"
|
|
||||||
|
|
||||||
msgid "No keys recorded yet"
|
msgid "No keys recorded yet"
|
||||||
msgstr "Noch keine Tasten aufgezeichnet"
|
msgstr "Noch keine Tasten aufgezeichnet"
|
||||||
@@ -30,6 +24,14 @@ msgstr "Noch keine Tasten aufgezeichnet"
|
|||||||
msgid "Capture key sequence"
|
msgid "Capture key sequence"
|
||||||
msgstr "Tastensequenz aufnehmen"
|
msgstr "Tastensequenz aufnehmen"
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"Press keys in order (modifiers like Ctrl, Alt, Shift, Super and AltGr are "
|
||||||
|
"recorded too). Click Done to apply or Cancel to discard."
|
||||||
|
msgstr ""
|
||||||
|
"Tasten der Reihe nach drücken (Modifikatoren wie Strg, Alt, Shift, Super und "
|
||||||
|
"AltGr werden mit aufgezeichnet). Mit Fertig übernehmen oder mit Abbrechen "
|
||||||
|
"verwerfen."
|
||||||
|
|
||||||
msgid "Clear"
|
msgid "Clear"
|
||||||
msgstr "Leeren"
|
msgstr "Leeren"
|
||||||
|
|
||||||
@@ -40,12 +42,120 @@ msgid "Done"
|
|||||||
msgstr "Fertig"
|
msgstr "Fertig"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Press keys in order (modifiers like Ctrl, Alt, Shift, Super and AltGr are "
|
"<b>The nine rows</b> – one per G key. Choose the type, then enter or capture "
|
||||||
"recorded too). Click Done to apply or Cancel to discard."
|
"the target and press <i>Save mappings</i>."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Tasten der Reihe nach drücken (Modifikatoren wie Strg, Alt, Shift, Super und "
|
"<b>Die neun Zeilen</b> – eine pro G-Taste. Typ wählen, Ziel eingeben oder "
|
||||||
"AltGr werden mit aufgezeichnet). Mit Fertig übernehmen oder mit Abbrechen "
|
"aufnehmen und <i>Mappings speichern</i> drücken."
|
||||||
"verwerfen."
|
|
||||||
|
msgid "<b>Key</b> – presses the captured sequence once, e.g. ctrl+c."
|
||||||
|
msgstr "<b>Taste</b> – drückt die aufgenommene Sequenz einmal, z. B. ctrl+c."
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"<b>Key (hold)</b> – presses it and keeps it down while the G key is held, e."
|
||||||
|
"g. for push-to-talk. Only a single step can be held."
|
||||||
|
msgstr ""
|
||||||
|
"<b>Taste (halten)</b> – drückt sie und hält sie, solange die G-Taste "
|
||||||
|
"gehalten wird, z. B. für Push-to-Talk. Nur ein einzelner Schritt kann "
|
||||||
|
"gehalten werden."
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"<b>Command</b> – runs a program or a command line, e.g. /usr/bin/firefox --"
|
||||||
|
"new-window."
|
||||||
|
msgstr ""
|
||||||
|
"<b>Befehl</b> – führt ein Programm oder eine Kommandozeile aus, z. B. /usr/"
|
||||||
|
"bin/firefox --new-window."
|
||||||
|
|
||||||
|
msgid "<b>Disabled</b> – the key does nothing."
|
||||||
|
msgstr "<b>Deaktiviert</b> – die Taste tut nichts."
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"<b>Capture</b> – records the keys you press next: press them, then <i>Done</"
|
||||||
|
"i>. Modifiers are kept (shift, altgr, …) and the names are layout "
|
||||||
|
"independent, so ctrl+z means the key that types z on your layout."
|
||||||
|
msgstr ""
|
||||||
|
"<b>Aufnehmen</b> – zeichnet die nächsten Tastenanschläge auf: drücken, dann "
|
||||||
|
"<i>Fertig</i>. Modifikatoren bleiben erhalten (shift, altgr, …) und die "
|
||||||
|
"Namen sind layoutunabhängig, ctrl+z bedeutet also die Taste, die auf deinem "
|
||||||
|
"Layout z tippt."
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"<b>Save mappings</b> – writes ~/.config/gKeyMapper/mappings.conf and makes "
|
||||||
|
"the daemon reload it. The menu entries <i>Export configuration...</i> and "
|
||||||
|
"<i>Import configuration...</i> do the same with a file you choose."
|
||||||
|
msgstr ""
|
||||||
|
"<b>Mappings speichern</b> – schreibt ~/.config/gKeyMapper/mappings.conf und "
|
||||||
|
"lässt den Daemon sie neu laden. Die Menüeinträge <i>Konfiguration "
|
||||||
|
"exportieren...</i> und <i>Konfiguration importieren...</i> machen dasselbe "
|
||||||
|
"mit einer Datei deiner Wahl."
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"<b>Daemon status</b> – the footer shows whether gkeymapper-daemon is "
|
||||||
|
"running, which keyboard it found and the last G key that fired. <i>Options</"
|
||||||
|
"i> shows the same in more detail."
|
||||||
|
msgstr ""
|
||||||
|
"<b>Daemon-Status</b> – die Fußzeile zeigt, ob gkeymapper-daemon läuft, "
|
||||||
|
"welche Tastatur gefunden wurde und welche G-Taste zuletzt ausgelöst hat. "
|
||||||
|
"<i>Optionen</i> zeigt dasselbe ausführlicher."
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"<b>Autostart</b> – <i>Start with the session</i> opens this window minimized "
|
||||||
|
"to the tray when you log in. The G keys work without it: gkeymapper-daemon "
|
||||||
|
"(a user service) reads them and replays the actions, and that daemon is the "
|
||||||
|
"only part that has to run."
|
||||||
|
msgstr ""
|
||||||
|
"<b>Autostart</b> – <i>Beim Anmelden starten</i> startet dieses Fenster "
|
||||||
|
"minimiert im Infobereich. Die G-Tasten funktionieren auch ohne: gkeymapper-"
|
||||||
|
"daemon (ein Benutzerdienst) liest sie und spielt die Aktionen ab – nur "
|
||||||
|
"dieser Daemon muss laufen."
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"<b>Tray and window</b> – with <i>Minimize to tray</i> the window is hidden "
|
||||||
|
"instead of closed when you close or minimize it; the tray icon shows it "
|
||||||
|
"again (<i>Show</i>) and quits the program (<i>Close</i>). Starting "
|
||||||
|
"gKeyMapper again closes the old window – rows that were not saved yet come "
|
||||||
|
"along."
|
||||||
|
msgstr ""
|
||||||
|
"<b>Infobereich und Fenster</b> – mit <i>In den Infobereich minimieren</i> "
|
||||||
|
"wird das Fenster beim Schließen oder Minimieren versteckt statt geschlossen; "
|
||||||
|
"das Symbol im Infobereich zeigt es wieder (<i>Anzeigen</i>) und beendet das "
|
||||||
|
"Programm (<i>Beenden</i>). Ein erneuter Start von gKeyMapper schließt das "
|
||||||
|
"alte Fenster – noch nicht gespeicherte Zeilen kommen dabei mit."
|
||||||
|
|
||||||
|
msgid "Help"
|
||||||
|
msgstr "Hilfe"
|
||||||
|
|
||||||
|
msgid "Close window"
|
||||||
|
msgstr "Fenster schließen"
|
||||||
|
|
||||||
|
msgid "Daemon: not reachable"
|
||||||
|
msgstr "Daemon: nicht erreichbar"
|
||||||
|
|
||||||
|
msgid "Daemon: running"
|
||||||
|
msgstr "Daemon: läuft"
|
||||||
|
|
||||||
|
msgid "Device: not found"
|
||||||
|
msgstr "Gerät: nicht gefunden"
|
||||||
|
|
||||||
|
#, c-format
|
||||||
|
msgid "Device: %s"
|
||||||
|
msgstr "Gerät: %s"
|
||||||
|
|
||||||
|
msgid "Configuration error"
|
||||||
|
msgstr "Konfigurationsfehler"
|
||||||
|
|
||||||
|
#, c-format
|
||||||
|
msgid "Last key: %s"
|
||||||
|
msgstr "Letzte Taste: %s"
|
||||||
|
|
||||||
|
msgid "Choose file"
|
||||||
|
msgstr "Datei auswählen"
|
||||||
|
|
||||||
|
msgid "_Cancel"
|
||||||
|
msgstr "_Abbrechen"
|
||||||
|
|
||||||
|
msgid "_Open"
|
||||||
|
msgstr "_Öffnen"
|
||||||
|
|
||||||
msgid "Choose file..."
|
msgid "Choose file..."
|
||||||
msgstr "Datei auswählen..."
|
msgstr "Datei auswählen..."
|
||||||
@@ -56,12 +166,12 @@ msgstr "z. B. /usr/bin/firefox oder eigener Befehl"
|
|||||||
msgid "Capture"
|
msgid "Capture"
|
||||||
msgstr "Aufnehmen"
|
msgstr "Aufnehmen"
|
||||||
|
|
||||||
msgid "e.g. ctrl+c, ctrl+v"
|
|
||||||
msgstr "z. B. Strg+C, Strg+V"
|
|
||||||
|
|
||||||
msgid "e.g. ctrl+w (held while the G key is held)"
|
msgid "e.g. ctrl+w (held while the G key is held)"
|
||||||
msgstr "z. B. Strg+W (gehalten, solange die G-Taste gedrückt ist)"
|
msgstr "z. B. Strg+W (gehalten, solange die G-Taste gedrückt ist)"
|
||||||
|
|
||||||
|
msgid "e.g. ctrl+c, ctrl+v"
|
||||||
|
msgstr "z. B. Strg+C, Strg+V"
|
||||||
|
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Could not save %s.\n"
|
msgid "Could not save %s.\n"
|
||||||
msgstr "Konnte %s nicht speichern.\n"
|
msgstr "Konnte %s nicht speichern.\n"
|
||||||
@@ -69,19 +179,12 @@ msgstr "Konnte %s nicht speichern.\n"
|
|||||||
msgid "Mappings saved to "
|
msgid "Mappings saved to "
|
||||||
msgstr "Mappings gespeichert nach "
|
msgstr "Mappings gespeichert nach "
|
||||||
|
|
||||||
#, c-format
|
|
||||||
msgid "Could not open %s.\n"
|
|
||||||
msgstr "Konnte %s nicht öffnen.\n"
|
|
||||||
|
|
||||||
msgid "Could not export mappings.\n"
|
msgid "Could not export mappings.\n"
|
||||||
msgstr "Mappings konnten nicht exportiert werden.\n"
|
msgstr "Mappings konnten nicht exportiert werden.\n"
|
||||||
|
|
||||||
msgid "gKeyMapper"
|
msgid "gKeyMapper"
|
||||||
msgstr "gKeyMapper"
|
msgstr "gKeyMapper"
|
||||||
|
|
||||||
msgid "G-Key mapper for the Logitech G910 (GTK4)."
|
|
||||||
msgstr "G-Key-Mapper für die Logitech G910 (GTK4)."
|
|
||||||
|
|
||||||
msgid "Export configuration"
|
msgid "Export configuration"
|
||||||
msgstr "Konfiguration exportieren"
|
msgstr "Konfiguration exportieren"
|
||||||
|
|
||||||
@@ -100,9 +203,15 @@ msgstr "Konfiguration exportieren..."
|
|||||||
msgid "Import configuration..."
|
msgid "Import configuration..."
|
||||||
msgstr "Konfiguration importieren..."
|
msgstr "Konfiguration importieren..."
|
||||||
|
|
||||||
|
msgid "Options"
|
||||||
|
msgstr "Optionen"
|
||||||
|
|
||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "Über"
|
msgstr "Über"
|
||||||
|
|
||||||
|
msgid "Quit"
|
||||||
|
msgstr "Beenden"
|
||||||
|
|
||||||
msgid "G1 to G9 of the Logitech G910 remapped to keys or commands"
|
msgid "G1 to G9 of the Logitech G910 remapped to keys or commands"
|
||||||
msgstr "G1 bis G9 der Logitech G910 auf Tasten oder Befehle ummappen"
|
msgstr "G1 bis G9 der Logitech G910 auf Tasten oder Befehle ummappen"
|
||||||
|
|
||||||
@@ -136,44 +245,12 @@ msgstr "Mappings speichern"
|
|||||||
msgid "Saved to ~/.config/gKeyMapper/mappings.conf"
|
msgid "Saved to ~/.config/gKeyMapper/mappings.conf"
|
||||||
msgstr "Gespeichert in ~/.config/gKeyMapper/mappings.conf"
|
msgstr "Gespeichert in ~/.config/gKeyMapper/mappings.conf"
|
||||||
|
|
||||||
msgid "Daemon: not reachable"
|
|
||||||
msgstr "Daemon: nicht erreichbar"
|
|
||||||
|
|
||||||
msgid "Daemon: running"
|
|
||||||
msgstr "Daemon: läuft"
|
|
||||||
|
|
||||||
msgid "Device: %s"
|
|
||||||
msgstr "Gerät: %s"
|
|
||||||
|
|
||||||
msgid "Device: not found"
|
|
||||||
msgstr "Gerät: nicht gefunden"
|
|
||||||
|
|
||||||
msgid "Configuration error"
|
|
||||||
msgstr "Konfigurationsfehler"
|
|
||||||
|
|
||||||
msgid "Last key: %s"
|
|
||||||
msgstr "Letzte Taste: %s"
|
|
||||||
|
|
||||||
msgid "Daemon: checking..."
|
msgid "Daemon: checking..."
|
||||||
msgstr "Daemon: wird geprüft ..."
|
msgstr "Daemon: wird geprüft ..."
|
||||||
|
|
||||||
msgid "Options"
|
#, c-format
|
||||||
msgstr "Optionen"
|
msgid "Could not open %s.\n"
|
||||||
|
msgstr "Konnte %s nicht öffnen.\n"
|
||||||
msgid "Help"
|
|
||||||
msgstr "Hilfe"
|
|
||||||
|
|
||||||
msgid "Quit"
|
|
||||||
msgstr "Beenden"
|
|
||||||
|
|
||||||
msgid "Show"
|
|
||||||
msgstr "Anzeigen"
|
|
||||||
|
|
||||||
msgid "Close"
|
|
||||||
msgstr "Beenden"
|
|
||||||
|
|
||||||
msgid "Close window"
|
|
||||||
msgstr "Fenster schließen"
|
|
||||||
|
|
||||||
msgid "Window"
|
msgid "Window"
|
||||||
msgstr "Fenster"
|
msgstr "Fenster"
|
||||||
@@ -181,13 +258,15 @@ msgstr "Fenster"
|
|||||||
msgid "Minimize to tray"
|
msgid "Minimize to tray"
|
||||||
msgstr "In den Infobereich minimieren"
|
msgstr "In den Infobereich minimieren"
|
||||||
|
|
||||||
|
msgid "Start with the session"
|
||||||
|
msgstr "Beim Anmelden starten"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Closing or minimizing the window hides it instead of quitting. The tray icon "
|
"Opens this window minimized to the tray when you log in. Not needed for the "
|
||||||
"shows it again (Show) and quits the program (Close)."
|
"G keys - the daemon does the work and is the only part that has to run."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Beim Schließen oder Minimieren wird das Fenster versteckt statt beendet. Das "
|
"Öffnet dieses Fenster beim Anmelden minimiert im Infobereich. Für die G-"
|
||||||
"Symbol im Infobereich zeigt es wieder (Anzeigen) und beendet das Programm "
|
"Tasten nicht nötig – die Arbeit macht der Daemon, und nur der muss laufen."
|
||||||
"(Beenden)."
|
|
||||||
|
|
||||||
msgid "Language"
|
msgid "Language"
|
||||||
msgstr "Sprache"
|
msgstr "Sprache"
|
||||||
@@ -198,21 +277,6 @@ msgstr "Systemsprache"
|
|||||||
msgid "The interface switches right away, including the tray menu."
|
msgid "The interface switches right away, including the tray menu."
|
||||||
msgstr "Die Oberfläche wechselt sofort – auch das Menü im Infobereich."
|
msgstr "Die Oberfläche wechselt sofort – auch das Menü im Infobereich."
|
||||||
|
|
||||||
msgid "Start with the session"
|
|
||||||
msgstr "Beim Anmelden starten"
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"Opens this window when you log in. Not needed for the G keys - the daemon "
|
|
||||||
"does the work and is the only part that has to run."
|
|
||||||
msgstr ""
|
|
||||||
"Öffnet dieses Fenster beim Anmelden. Für die G-Tasten nicht nötig – die "
|
|
||||||
"Arbeit macht der Daemon, und nur der muss laufen."
|
|
||||||
|
|
||||||
msgid "No tray is available on this desktop, so the window cannot be hidden."
|
|
||||||
msgstr ""
|
|
||||||
"Auf diesem Desktop gibt es keinen Infobereich, daher kann das Fenster nicht "
|
|
||||||
"versteckt werden."
|
|
||||||
|
|
||||||
msgid "Daemon status"
|
msgid "Daemon status"
|
||||||
msgstr "Daemon-Status"
|
msgstr "Daemon-Status"
|
||||||
|
|
||||||
@@ -234,6 +298,19 @@ msgstr "Konfigurationsdatei"
|
|||||||
msgid "Last key"
|
msgid "Last key"
|
||||||
msgstr "Letzte Taste"
|
msgstr "Letzte Taste"
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"Closing or minimizing the window hides it instead of quitting. The tray icon "
|
||||||
|
"shows it again (Show) and quits the program (Close)."
|
||||||
|
msgstr ""
|
||||||
|
"Beim Schließen oder Minimieren wird das Fenster versteckt statt beendet. Das "
|
||||||
|
"Symbol im Infobereich zeigt es wieder (Anzeigen) und beendet das Programm "
|
||||||
|
"(Beenden)."
|
||||||
|
|
||||||
|
msgid "No tray is available on this desktop, so the window cannot be hidden."
|
||||||
|
msgstr ""
|
||||||
|
"Auf diesem Desktop gibt es keinen Infobereich, daher kann das Fenster nicht "
|
||||||
|
"versteckt werden."
|
||||||
|
|
||||||
msgid "active"
|
msgid "active"
|
||||||
msgstr "aktiv"
|
msgstr "aktiv"
|
||||||
|
|
||||||
@@ -249,82 +326,8 @@ msgstr "nicht erreichbar"
|
|||||||
msgid "not found"
|
msgid "not found"
|
||||||
msgstr "nicht gefunden"
|
msgstr "nicht gefunden"
|
||||||
|
|
||||||
msgid ""
|
msgid "Show"
|
||||||
"<b>The nine rows</b> – one per G key. Choose the type, then enter or capture "
|
msgstr "Anzeigen"
|
||||||
"the target and press <i>Save mappings</i>."
|
|
||||||
msgstr ""
|
|
||||||
"<b>Die neun Zeilen</b> – eine pro G-Taste. Typ wählen, Ziel eingeben oder "
|
|
||||||
"aufnehmen und <i>Mappings speichern</i> drücken."
|
|
||||||
|
|
||||||
msgid "<b>Key</b> – presses the captured sequence once, e.g. ctrl+c."
|
msgid "Close"
|
||||||
msgstr "<b>Taste</b> – drückt die aufgenommene Sequenz einmal, z. B. ctrl+c."
|
msgstr "Beenden"
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"<b>Key (hold)</b> – presses it and keeps it down while the G key is held, "
|
|
||||||
"e.g. for push-to-talk. Only a single step can be held."
|
|
||||||
msgstr ""
|
|
||||||
"<b>Taste (halten)</b> – drückt sie und hält sie, solange die G-Taste "
|
|
||||||
"gehalten wird, z. B. für Push-to-Talk. Nur ein einzelner Schritt kann "
|
|
||||||
"gehalten werden."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"<b>Command</b> – runs a program or a command line, e.g. /usr/bin/firefox "
|
|
||||||
"--new-window."
|
|
||||||
msgstr ""
|
|
||||||
"<b>Befehl</b> – führt ein Programm oder eine Kommandozeile aus, z. B. "
|
|
||||||
"/usr/bin/firefox --new-window."
|
|
||||||
|
|
||||||
msgid "<b>Disabled</b> – the key does nothing."
|
|
||||||
msgstr "<b>Deaktiviert</b> – die Taste tut nichts."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"<b>Capture</b> – records the keys you press next: press them, then "
|
|
||||||
"<i>Done</i>. Modifiers are kept (shift, altgr, …) and the names are layout "
|
|
||||||
"independent, so ctrl+z means the key that types z on your layout."
|
|
||||||
msgstr ""
|
|
||||||
"<b>Aufnehmen</b> – zeichnet die nächsten Tastenanschläge auf: drücken, dann "
|
|
||||||
"<i>Fertig</i>. Modifikatoren bleiben erhalten (shift, altgr, …) und die "
|
|
||||||
"Namen sind layoutunabhängig, ctrl+z bedeutet also die Taste, die auf deinem "
|
|
||||||
"Layout z tippt."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"<b>Save mappings</b> – writes ~/.config/gKeyMapper/mappings.conf and makes "
|
|
||||||
"the daemon reload it. The menu entries <i>Export configuration...</i> and "
|
|
||||||
"<i>Import configuration...</i> do the same with a file you choose."
|
|
||||||
msgstr ""
|
|
||||||
"<b>Mappings speichern</b> – schreibt ~/.config/gKeyMapper/mappings.conf und "
|
|
||||||
"lässt den Daemon sie neu laden. Die Menüeinträge <i>Konfiguration "
|
|
||||||
"exportieren...</i> und <i>Konfiguration importieren...</i> machen dasselbe "
|
|
||||||
"mit einer Datei deiner Wahl."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"<b>Daemon status</b> – the footer shows whether gkeymapper-daemon is "
|
|
||||||
"running, which keyboard it found and the last G key that fired. "
|
|
||||||
"<i>Options</i> shows the same in more detail."
|
|
||||||
msgstr ""
|
|
||||||
"<b>Daemon-Status</b> – die Fußzeile zeigt, ob gkeymapper-daemon läuft, "
|
|
||||||
"welche Tastatur gefunden wurde und welche G-Taste zuletzt ausgelöst hat. "
|
|
||||||
"<i>Optionen</i> zeigt dasselbe ausführlicher."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"<b>Autostart</b> – <i>Start with the session</i> only opens this window when "
|
|
||||||
"you log in. The G keys work without it: gkeymapper-daemon (a user service) "
|
|
||||||
"reads them and replays the actions, and that daemon is the only part that has "
|
|
||||||
"to run."
|
|
||||||
msgstr ""
|
|
||||||
"<b>Autostart</b> – <i>Beim Anmelden starten</i> öffnet nur dieses Fenster. "
|
|
||||||
"Die G-Tasten funktionieren auch ohne: gkeymapper-daemon (ein Benutzerdienst) "
|
|
||||||
"liest sie und spielt die Aktionen ab – nur dieser Daemon muss laufen."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"<b>Tray and window</b> – with <i>Minimize to tray</i> the window is hidden "
|
|
||||||
"instead of closed when you close or minimize it; the tray icon shows it "
|
|
||||||
"again (<i>Show</i>) and quits the program (<i>Close</i>). Starting "
|
|
||||||
"gKeyMapper again closes the old window – rows that were not saved are lost "
|
|
||||||
"with it."
|
|
||||||
msgstr ""
|
|
||||||
"<b>Infobereich und Fenster</b> – mit <i>In den Infobereich minimieren</i> "
|
|
||||||
"wird das Fenster beim Schließen oder Minimieren versteckt statt geschlossen; "
|
|
||||||
"das Symbol im Infobereich zeigt es wieder (<i>Anzeigen</i>) und beendet das "
|
|
||||||
"Programm (<i>Beenden</i>). Ein erneuter Start von gKeyMapper schließt das "
|
|
||||||
"alte Fenster – nicht gespeicherte Zeilen gehen damit verloren."
|
|
||||||
|
|||||||
304
po/en.po
304
po/en.po
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: gkeymapper 0.1\n"
|
"Project-Id-Version: gkeymapper 0.1\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-18 03:08+0200\n"
|
"POT-Creation-Date: 2026-10-18 20:18+0200\n"
|
||||||
"PO-Revision-Date: 2026-10-18 03:08+0200\n"
|
"PO-Revision-Date: 2026-10-18 03:08+0200\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: English\n"
|
"Language-Team: English\n"
|
||||||
@@ -16,14 +16,8 @@ msgstr ""
|
|||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
msgid "Choose file"
|
msgid "G-Key mapper for the Logitech G910 (GTK4)."
|
||||||
msgstr "Choose file"
|
msgstr "G-Key mapper for the Logitech G910 (GTK4)."
|
||||||
|
|
||||||
msgid "_Cancel"
|
|
||||||
msgstr "_Cancel"
|
|
||||||
|
|
||||||
msgid "_Open"
|
|
||||||
msgstr "_Open"
|
|
||||||
|
|
||||||
msgid "No keys recorded yet"
|
msgid "No keys recorded yet"
|
||||||
msgstr "No keys recorded yet"
|
msgstr "No keys recorded yet"
|
||||||
@@ -31,6 +25,13 @@ msgstr "No keys recorded yet"
|
|||||||
msgid "Capture key sequence"
|
msgid "Capture key sequence"
|
||||||
msgstr "Capture key sequence"
|
msgstr "Capture key sequence"
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"Press keys in order (modifiers like Ctrl, Alt, Shift, Super and AltGr are "
|
||||||
|
"recorded too). Click Done to apply or Cancel to discard."
|
||||||
|
msgstr ""
|
||||||
|
"Press keys in order (modifiers like Ctrl, Alt, Shift, Super and AltGr are "
|
||||||
|
"recorded too). Click Done to apply or Cancel to discard."
|
||||||
|
|
||||||
msgid "Clear"
|
msgid "Clear"
|
||||||
msgstr "Clear"
|
msgstr "Clear"
|
||||||
|
|
||||||
@@ -41,11 +42,117 @@ msgid "Done"
|
|||||||
msgstr "Done"
|
msgstr "Done"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Press keys in order (modifiers like Ctrl, Alt, Shift, Super and AltGr are "
|
"<b>The nine rows</b> – one per G key. Choose the type, then enter or capture "
|
||||||
"recorded too). Click Done to apply or Cancel to discard."
|
"the target and press <i>Save mappings</i>."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Press keys in order (modifiers like Ctrl, Alt, Shift, Super and AltGr are "
|
"<b>The nine rows</b> – one per G key. Choose the type, then enter or capture "
|
||||||
"recorded too). Click Done to apply or Cancel to discard."
|
"the target and press <i>Save mappings</i>."
|
||||||
|
|
||||||
|
msgid "<b>Key</b> – presses the captured sequence once, e.g. ctrl+c."
|
||||||
|
msgstr "<b>Key</b> – presses the captured sequence once, e.g. ctrl+c."
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"<b>Key (hold)</b> – presses it and keeps it down while the G key is held, e."
|
||||||
|
"g. for push-to-talk. Only a single step can be held."
|
||||||
|
msgstr ""
|
||||||
|
"<b>Key (hold)</b> – presses it and keeps it down while the G key is held, e."
|
||||||
|
"g. for push-to-talk. Only a single step can be held."
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"<b>Command</b> – runs a program or a command line, e.g. /usr/bin/firefox --"
|
||||||
|
"new-window."
|
||||||
|
msgstr ""
|
||||||
|
"<b>Command</b> – runs a program or a command line, e.g. /usr/bin/firefox --"
|
||||||
|
"new-window."
|
||||||
|
|
||||||
|
msgid "<b>Disabled</b> – the key does nothing."
|
||||||
|
msgstr "<b>Disabled</b> – the key does nothing."
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"<b>Capture</b> – records the keys you press next: press them, then <i>Done</"
|
||||||
|
"i>. Modifiers are kept (shift, altgr, …) and the names are layout "
|
||||||
|
"independent, so ctrl+z means the key that types z on your layout."
|
||||||
|
msgstr ""
|
||||||
|
"<b>Capture</b> – records the keys you press next: press them, then <i>Done</"
|
||||||
|
"i>. Modifiers are kept (shift, altgr, …) and the names are layout "
|
||||||
|
"independent, so ctrl+z means the key that types z on your layout."
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"<b>Save mappings</b> – writes ~/.config/gKeyMapper/mappings.conf and makes "
|
||||||
|
"the daemon reload it. The menu entries <i>Export configuration...</i> and "
|
||||||
|
"<i>Import configuration...</i> do the same with a file you choose."
|
||||||
|
msgstr ""
|
||||||
|
"<b>Save mappings</b> – writes ~/.config/gKeyMapper/mappings.conf and makes "
|
||||||
|
"the daemon reload it. The menu entries <i>Export configuration...</i> and "
|
||||||
|
"<i>Import configuration...</i> do the same with a file you choose."
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"<b>Daemon status</b> – the footer shows whether gkeymapper-daemon is "
|
||||||
|
"running, which keyboard it found and the last G key that fired. <i>Options</"
|
||||||
|
"i> shows the same in more detail."
|
||||||
|
msgstr ""
|
||||||
|
"<b>Daemon status</b> – the footer shows whether gkeymapper-daemon is "
|
||||||
|
"running, which keyboard it found and the last G key that fired. <i>Options</"
|
||||||
|
"i> shows the same in more detail."
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"<b>Autostart</b> – <i>Start with the session</i> opens this window minimized "
|
||||||
|
"to the tray when you log in. The G keys work without it: gkeymapper-daemon "
|
||||||
|
"(a user service) reads them and replays the actions, and that daemon is the "
|
||||||
|
"only part that has to run."
|
||||||
|
msgstr ""
|
||||||
|
"<b>Autostart</b> – <i>Start with the session</i> opens this window minimized "
|
||||||
|
"to the tray when you log in. The G keys work without it: gkeymapper-daemon "
|
||||||
|
"(a user service) reads them and replays the actions, and that daemon is the "
|
||||||
|
"only part that has to run."
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"<b>Tray and window</b> – with <i>Minimize to tray</i> the window is hidden "
|
||||||
|
"instead of closed when you close or minimize it; the tray icon shows it "
|
||||||
|
"again (<i>Show</i>) and quits the program (<i>Close</i>). Starting "
|
||||||
|
"gKeyMapper again closes the old window – rows that were not saved yet come "
|
||||||
|
"along."
|
||||||
|
msgstr ""
|
||||||
|
"<b>Tray and window</b> – with <i>Minimize to tray</i> the window is hidden "
|
||||||
|
"instead of closed when you close or minimize it; the tray icon shows it "
|
||||||
|
"again (<i>Show</i>) and quits the program (<i>Close</i>). Starting "
|
||||||
|
"gKeyMapper again closes the old window – rows that were not saved yet come "
|
||||||
|
"along."
|
||||||
|
|
||||||
|
msgid "Help"
|
||||||
|
msgstr "Help"
|
||||||
|
|
||||||
|
msgid "Close window"
|
||||||
|
msgstr "Close window"
|
||||||
|
|
||||||
|
msgid "Daemon: not reachable"
|
||||||
|
msgstr "Daemon: not reachable"
|
||||||
|
|
||||||
|
msgid "Daemon: running"
|
||||||
|
msgstr "Daemon: running"
|
||||||
|
|
||||||
|
msgid "Device: not found"
|
||||||
|
msgstr "Device: not found"
|
||||||
|
|
||||||
|
#, c-format
|
||||||
|
msgid "Device: %s"
|
||||||
|
msgstr "Device: %s"
|
||||||
|
|
||||||
|
msgid "Configuration error"
|
||||||
|
msgstr "Configuration error"
|
||||||
|
|
||||||
|
#, c-format
|
||||||
|
msgid "Last key: %s"
|
||||||
|
msgstr "Last key: %s"
|
||||||
|
|
||||||
|
msgid "Choose file"
|
||||||
|
msgstr "Choose file"
|
||||||
|
|
||||||
|
msgid "_Cancel"
|
||||||
|
msgstr "_Cancel"
|
||||||
|
|
||||||
|
msgid "_Open"
|
||||||
|
msgstr "_Open"
|
||||||
|
|
||||||
msgid "Choose file..."
|
msgid "Choose file..."
|
||||||
msgstr "Choose file..."
|
msgstr "Choose file..."
|
||||||
@@ -56,12 +163,12 @@ msgstr "e.g. /usr/bin/firefox or custom command"
|
|||||||
msgid "Capture"
|
msgid "Capture"
|
||||||
msgstr "Capture"
|
msgstr "Capture"
|
||||||
|
|
||||||
msgid "e.g. ctrl+c, ctrl+v"
|
|
||||||
msgstr "e.g. ctrl+c, ctrl+v"
|
|
||||||
|
|
||||||
msgid "e.g. ctrl+w (held while the G key is held)"
|
msgid "e.g. ctrl+w (held while the G key is held)"
|
||||||
msgstr "e.g. ctrl+w (held while the G key is held)"
|
msgstr "e.g. ctrl+w (held while the G key is held)"
|
||||||
|
|
||||||
|
msgid "e.g. ctrl+c, ctrl+v"
|
||||||
|
msgstr "e.g. ctrl+c, ctrl+v"
|
||||||
|
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Could not save %s.\n"
|
msgid "Could not save %s.\n"
|
||||||
msgstr "Could not save %s.\n"
|
msgstr "Could not save %s.\n"
|
||||||
@@ -69,19 +176,12 @@ msgstr "Could not save %s.\n"
|
|||||||
msgid "Mappings saved to "
|
msgid "Mappings saved to "
|
||||||
msgstr "Mappings saved to "
|
msgstr "Mappings saved to "
|
||||||
|
|
||||||
#, c-format
|
|
||||||
msgid "Could not open %s.\n"
|
|
||||||
msgstr "Could not open %s.\n"
|
|
||||||
|
|
||||||
msgid "Could not export mappings.\n"
|
msgid "Could not export mappings.\n"
|
||||||
msgstr "Could not export mappings.\n"
|
msgstr "Could not export mappings.\n"
|
||||||
|
|
||||||
msgid "gKeyMapper"
|
msgid "gKeyMapper"
|
||||||
msgstr "gKeyMapper"
|
msgstr "gKeyMapper"
|
||||||
|
|
||||||
msgid "G-Key mapper for the Logitech G910 (GTK4)."
|
|
||||||
msgstr "G-Key mapper for the Logitech G910 (GTK4)."
|
|
||||||
|
|
||||||
msgid "Export configuration"
|
msgid "Export configuration"
|
||||||
msgstr "Export configuration"
|
msgstr "Export configuration"
|
||||||
|
|
||||||
@@ -100,9 +200,15 @@ msgstr "Export configuration..."
|
|||||||
msgid "Import configuration..."
|
msgid "Import configuration..."
|
||||||
msgstr "Import configuration..."
|
msgstr "Import configuration..."
|
||||||
|
|
||||||
|
msgid "Options"
|
||||||
|
msgstr "Options"
|
||||||
|
|
||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "About"
|
msgstr "About"
|
||||||
|
|
||||||
|
msgid "Quit"
|
||||||
|
msgstr "Quit"
|
||||||
|
|
||||||
msgid "G1 to G9 of the Logitech G910 remapped to keys or commands"
|
msgid "G1 to G9 of the Logitech G910 remapped to keys or commands"
|
||||||
msgstr "G1 to G9 of the Logitech G910 remapped to keys or commands"
|
msgstr "G1 to G9 of the Logitech G910 remapped to keys or commands"
|
||||||
|
|
||||||
@@ -136,44 +242,12 @@ msgstr "Save mappings"
|
|||||||
msgid "Saved to ~/.config/gKeyMapper/mappings.conf"
|
msgid "Saved to ~/.config/gKeyMapper/mappings.conf"
|
||||||
msgstr "Saved to ~/.config/gKeyMapper/mappings.conf"
|
msgstr "Saved to ~/.config/gKeyMapper/mappings.conf"
|
||||||
|
|
||||||
msgid "Daemon: not reachable"
|
|
||||||
msgstr "Daemon: not reachable"
|
|
||||||
|
|
||||||
msgid "Daemon: running"
|
|
||||||
msgstr "Daemon: running"
|
|
||||||
|
|
||||||
msgid "Device: %s"
|
|
||||||
msgstr "Device: %s"
|
|
||||||
|
|
||||||
msgid "Device: not found"
|
|
||||||
msgstr "Device: not found"
|
|
||||||
|
|
||||||
msgid "Configuration error"
|
|
||||||
msgstr "Configuration error"
|
|
||||||
|
|
||||||
msgid "Last key: %s"
|
|
||||||
msgstr "Last key: %s"
|
|
||||||
|
|
||||||
msgid "Daemon: checking..."
|
msgid "Daemon: checking..."
|
||||||
msgstr "Daemon: checking..."
|
msgstr "Daemon: checking..."
|
||||||
|
|
||||||
msgid "Options"
|
#, c-format
|
||||||
msgstr "Options"
|
msgid "Could not open %s.\n"
|
||||||
|
msgstr "Could not open %s.\n"
|
||||||
msgid "Help"
|
|
||||||
msgstr "Help"
|
|
||||||
|
|
||||||
msgid "Quit"
|
|
||||||
msgstr "Quit"
|
|
||||||
|
|
||||||
msgid "Show"
|
|
||||||
msgstr "Show"
|
|
||||||
|
|
||||||
msgid "Close"
|
|
||||||
msgstr "Close"
|
|
||||||
|
|
||||||
msgid "Close window"
|
|
||||||
msgstr "Close window"
|
|
||||||
|
|
||||||
msgid "Window"
|
msgid "Window"
|
||||||
msgstr "Window"
|
msgstr "Window"
|
||||||
@@ -181,12 +255,15 @@ msgstr "Window"
|
|||||||
msgid "Minimize to tray"
|
msgid "Minimize to tray"
|
||||||
msgstr "Minimize to tray"
|
msgstr "Minimize to tray"
|
||||||
|
|
||||||
|
msgid "Start with the session"
|
||||||
|
msgstr "Start with the session"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Closing or minimizing the window hides it instead of quitting. The tray icon "
|
"Opens this window minimized to the tray when you log in. Not needed for the "
|
||||||
"shows it again (Show) and quits the program (Close)."
|
"G keys - the daemon does the work and is the only part that has to run."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Closing or minimizing the window hides it instead of quitting. The tray icon "
|
"Opens this window minimized to the tray when you log in. Not needed for the "
|
||||||
"shows it again (Show) and quits the program (Close)."
|
"G keys - the daemon does the work and is the only part that has to run."
|
||||||
|
|
||||||
msgid "Language"
|
msgid "Language"
|
||||||
msgstr "Language"
|
msgstr "Language"
|
||||||
@@ -197,19 +274,6 @@ msgstr "System language"
|
|||||||
msgid "The interface switches right away, including the tray menu."
|
msgid "The interface switches right away, including the tray menu."
|
||||||
msgstr "The interface switches right away, including the tray menu."
|
msgstr "The interface switches right away, including the tray menu."
|
||||||
|
|
||||||
msgid "Start with the session"
|
|
||||||
msgstr "Start with the session"
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"Opens this window when you log in. Not needed for the G keys - the daemon "
|
|
||||||
"does the work and is the only part that has to run."
|
|
||||||
msgstr ""
|
|
||||||
"Opens this window when you log in. Not needed for the G keys - the daemon "
|
|
||||||
"does the work and is the only part that has to run."
|
|
||||||
|
|
||||||
msgid "No tray is available on this desktop, so the window cannot be hidden."
|
|
||||||
msgstr "No tray is available on this desktop, so the window cannot be hidden."
|
|
||||||
|
|
||||||
msgid "Daemon status"
|
msgid "Daemon status"
|
||||||
msgstr "Daemon status"
|
msgstr "Daemon status"
|
||||||
|
|
||||||
@@ -231,6 +295,16 @@ msgstr "Configuration file"
|
|||||||
msgid "Last key"
|
msgid "Last key"
|
||||||
msgstr "Last key"
|
msgstr "Last key"
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"Closing or minimizing the window hides it instead of quitting. The tray icon "
|
||||||
|
"shows it again (Show) and quits the program (Close)."
|
||||||
|
msgstr ""
|
||||||
|
"Closing or minimizing the window hides it instead of quitting. The tray icon "
|
||||||
|
"shows it again (Show) and quits the program (Close)."
|
||||||
|
|
||||||
|
msgid "No tray is available on this desktop, so the window cannot be hidden."
|
||||||
|
msgstr "No tray is available on this desktop, so the window cannot be hidden."
|
||||||
|
|
||||||
msgid "active"
|
msgid "active"
|
||||||
msgstr "active"
|
msgstr "active"
|
||||||
|
|
||||||
@@ -246,80 +320,8 @@ msgstr "not reachable"
|
|||||||
msgid "not found"
|
msgid "not found"
|
||||||
msgstr "not found"
|
msgstr "not found"
|
||||||
|
|
||||||
msgid ""
|
msgid "Show"
|
||||||
"<b>The nine rows</b> – one per G key. Choose the type, then enter or capture "
|
msgstr "Show"
|
||||||
"the target and press <i>Save mappings</i>."
|
|
||||||
msgstr ""
|
|
||||||
"<b>The nine rows</b> – one per G key. Choose the type, then enter or capture "
|
|
||||||
"the target and press <i>Save mappings</i>."
|
|
||||||
|
|
||||||
msgid "<b>Key</b> – presses the captured sequence once, e.g. ctrl+c."
|
msgid "Close"
|
||||||
msgstr "<b>Key</b> – presses the captured sequence once, e.g. ctrl+c."
|
msgstr "Close"
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"<b>Key (hold)</b> – presses it and keeps it down while the G key is held, "
|
|
||||||
"e.g. for push-to-talk. Only a single step can be held."
|
|
||||||
msgstr ""
|
|
||||||
"<b>Key (hold)</b> – presses it and keeps it down while the G key is held, "
|
|
||||||
"e.g. for push-to-talk. Only a single step can be held."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"<b>Command</b> – runs a program or a command line, e.g. /usr/bin/firefox "
|
|
||||||
"--new-window."
|
|
||||||
msgstr ""
|
|
||||||
"<b>Command</b> – runs a program or a command line, e.g. /usr/bin/firefox "
|
|
||||||
"--new-window."
|
|
||||||
|
|
||||||
msgid "<b>Disabled</b> – the key does nothing."
|
|
||||||
msgstr "<b>Disabled</b> – the key does nothing."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"<b>Capture</b> – records the keys you press next: press them, then "
|
|
||||||
"<i>Done</i>. Modifiers are kept (shift, altgr, …) and the names are layout "
|
|
||||||
"independent, so ctrl+z means the key that types z on your layout."
|
|
||||||
msgstr ""
|
|
||||||
"<b>Capture</b> – records the keys you press next: press them, then "
|
|
||||||
"<i>Done</i>. Modifiers are kept (shift, altgr, …) and the names are layout "
|
|
||||||
"independent, so ctrl+z means the key that types z on your layout."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"<b>Save mappings</b> – writes ~/.config/gKeyMapper/mappings.conf and makes "
|
|
||||||
"the daemon reload it. The menu entries <i>Export configuration...</i> and "
|
|
||||||
"<i>Import configuration...</i> do the same with a file you choose."
|
|
||||||
msgstr ""
|
|
||||||
"<b>Save mappings</b> – writes ~/.config/gKeyMapper/mappings.conf and makes "
|
|
||||||
"the daemon reload it. The menu entries <i>Export configuration...</i> and "
|
|
||||||
"<i>Import configuration...</i> do the same with a file you choose."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"<b>Daemon status</b> – the footer shows whether gkeymapper-daemon is "
|
|
||||||
"running, which keyboard it found and the last G key that fired. "
|
|
||||||
"<i>Options</i> shows the same in more detail."
|
|
||||||
msgstr ""
|
|
||||||
"<b>Daemon status</b> – the footer shows whether gkeymapper-daemon is "
|
|
||||||
"running, which keyboard it found and the last G key that fired. "
|
|
||||||
"<i>Options</i> shows the same in more detail."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"<b>Autostart</b> – <i>Start with the session</i> only opens this window when "
|
|
||||||
"you log in. The G keys work without it: gkeymapper-daemon (a user service) "
|
|
||||||
"reads them and replays the actions, and that daemon is the only part that has "
|
|
||||||
"to run."
|
|
||||||
msgstr ""
|
|
||||||
"<b>Autostart</b> – <i>Start with the session</i> only opens this window when "
|
|
||||||
"you log in. The G keys work without it: gkeymapper-daemon (a user service) "
|
|
||||||
"reads them and replays the actions, and that daemon is the only part that has "
|
|
||||||
"to run."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"<b>Tray and window</b> – with <i>Minimize to tray</i> the window is hidden "
|
|
||||||
"instead of closed when you close or minimize it; the tray icon shows it "
|
|
||||||
"again (<i>Show</i>) and quits the program (<i>Close</i>). Starting "
|
|
||||||
"gKeyMapper again closes the old window – rows that were not saved are lost "
|
|
||||||
"with it."
|
|
||||||
msgstr ""
|
|
||||||
"<b>Tray and window</b> – with <i>Minimize to tray</i> the window is hidden "
|
|
||||||
"instead of closed when you close or minimize it; the tray icon shows it "
|
|
||||||
"again (<i>Show</i>) and quits the program (<i>Close</i>). Starting "
|
|
||||||
"gKeyMapper again closes the old window – rows that were not saved are lost "
|
|
||||||
"with it."
|
|
||||||
|
|||||||
313
po/es.po
313
po/es.po
@@ -4,7 +4,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: gkeymapper 0.1\n"
|
"Project-Id-Version: gkeymapper 0.1\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-18 03:08+0200\n"
|
"POT-Creation-Date: 2026-10-18 20:18+0200\n"
|
||||||
"PO-Revision-Date: 2026-10-18 03:08+0200\n"
|
"PO-Revision-Date: 2026-10-18 03:08+0200\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Spanish\n"
|
"Language-Team: Spanish\n"
|
||||||
@@ -13,14 +13,8 @@ msgstr ""
|
|||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
msgid "Choose file"
|
msgid "G-Key mapper for the Logitech G910 (GTK4)."
|
||||||
msgstr "Elegir archivo"
|
msgstr "Asignador de teclas G para el Logitech G910 (GTK4)."
|
||||||
|
|
||||||
msgid "_Cancel"
|
|
||||||
msgstr "_Cancelar"
|
|
||||||
|
|
||||||
msgid "_Open"
|
|
||||||
msgstr "_Abrir"
|
|
||||||
|
|
||||||
msgid "No keys recorded yet"
|
msgid "No keys recorded yet"
|
||||||
msgstr "Aún no se ha registrado ninguna tecla"
|
msgstr "Aún no se ha registrado ninguna tecla"
|
||||||
@@ -28,6 +22,14 @@ msgstr "Aún no se ha registrado ninguna tecla"
|
|||||||
msgid "Capture key sequence"
|
msgid "Capture key sequence"
|
||||||
msgstr "Capturar secuencia de teclas"
|
msgstr "Capturar secuencia de teclas"
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"Press keys in order (modifiers like Ctrl, Alt, Shift, Super and AltGr are "
|
||||||
|
"recorded too). Click Done to apply or Cancel to discard."
|
||||||
|
msgstr ""
|
||||||
|
"Pulsa las teclas en orden (los modificadores como Ctrl, Alt, Mayús, Super y "
|
||||||
|
"AltGr también se registran). Haz clic en Aceptar para aplicar o en Cancelar "
|
||||||
|
"para descartar."
|
||||||
|
|
||||||
msgid "Clear"
|
msgid "Clear"
|
||||||
msgstr "Borrar"
|
msgstr "Borrar"
|
||||||
|
|
||||||
@@ -38,12 +40,119 @@ msgid "Done"
|
|||||||
msgstr "Aceptar"
|
msgstr "Aceptar"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Press keys in order (modifiers like Ctrl, Alt, Shift, Super and AltGr are "
|
"<b>The nine rows</b> – one per G key. Choose the type, then enter or capture "
|
||||||
"recorded too). Click Done to apply or Cancel to discard."
|
"the target and press <i>Save mappings</i>."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Pulsa las teclas en orden (los modificadores como Ctrl, Alt, Mayús, Super y "
|
"<b>Las nueve filas</b> – una por tecla G. Elige el tipo, introduce o captura "
|
||||||
"AltGr también se registran). Haz clic en Aceptar para aplicar o en Cancelar "
|
"el destino y pulsa <i>Guardar asignaciones</i>."
|
||||||
"para descartar."
|
|
||||||
|
msgid "<b>Key</b> – presses the captured sequence once, e.g. ctrl+c."
|
||||||
|
msgstr "<b>Tecla</b> – pulsa la secuencia capturada una vez, p. ej. ctrl+c."
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"<b>Key (hold)</b> – presses it and keeps it down while the G key is held, e."
|
||||||
|
"g. for push-to-talk. Only a single step can be held."
|
||||||
|
msgstr ""
|
||||||
|
"<b>Tecla (mantener)</b> – la pulsa y la mantiene mientras se mantiene la "
|
||||||
|
"tecla G, p. ej. para pulsar para hablar. Solo se puede mantener un paso."
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"<b>Command</b> – runs a program or a command line, e.g. /usr/bin/firefox --"
|
||||||
|
"new-window."
|
||||||
|
msgstr ""
|
||||||
|
"<b>Comando</b> – ejecuta un programa o una línea de comandos, p. ej. /usr/"
|
||||||
|
"bin/firefox --new-window."
|
||||||
|
|
||||||
|
msgid "<b>Disabled</b> – the key does nothing."
|
||||||
|
msgstr "<b>Desactivado</b> – la tecla no hace nada."
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"<b>Capture</b> – records the keys you press next: press them, then <i>Done</"
|
||||||
|
"i>. Modifiers are kept (shift, altgr, …) and the names are layout "
|
||||||
|
"independent, so ctrl+z means the key that types z on your layout."
|
||||||
|
msgstr ""
|
||||||
|
"<b>Capturar</b> – graba las siguientes teclas que pulses: púlsalas y luego "
|
||||||
|
"<i>Aceptar</i>. Los modificadores se conservan (shift, altgr, …) y los "
|
||||||
|
"nombres no dependen de la distribución, así que ctrl+z es la tecla que "
|
||||||
|
"escribe z en tu distribución."
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"<b>Save mappings</b> – writes ~/.config/gKeyMapper/mappings.conf and makes "
|
||||||
|
"the daemon reload it. The menu entries <i>Export configuration...</i> and "
|
||||||
|
"<i>Import configuration...</i> do the same with a file you choose."
|
||||||
|
msgstr ""
|
||||||
|
"<b>Guardar asignaciones</b> – escribe ~/.config/gKeyMapper/mappings.conf y "
|
||||||
|
"hace que el daemon la recargue. Las entradas de menú <i>Exportar "
|
||||||
|
"configuración...</i> e <i>Importar configuración...</i> hacen lo mismo con "
|
||||||
|
"el archivo que elijas."
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"<b>Daemon status</b> – the footer shows whether gkeymapper-daemon is "
|
||||||
|
"running, which keyboard it found and the last G key that fired. <i>Options</"
|
||||||
|
"i> shows the same in more detail."
|
||||||
|
msgstr ""
|
||||||
|
"<b>Estado del daemon</b> – el pie de ventana muestra si gkeymapper-daemon "
|
||||||
|
"está en ejecución, qué teclado ha encontrado y la última tecla G que se ha "
|
||||||
|
"pulsado. <i>Opciones</i> muestra lo mismo con más detalle."
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"<b>Autostart</b> – <i>Start with the session</i> opens this window minimized "
|
||||||
|
"to the tray when you log in. The G keys work without it: gkeymapper-daemon "
|
||||||
|
"(a user service) reads them and replays the actions, and that daemon is the "
|
||||||
|
"only part that has to run."
|
||||||
|
msgstr ""
|
||||||
|
"<b>Inicio automático</b> – <i>Iniciar con la sesión</i> abre esta ventana "
|
||||||
|
"minimizada en la bandeja al iniciar la sesión. Las teclas G funcionan sin "
|
||||||
|
"ella: gkeymapper-daemon (un servicio de usuario) las lee y reproduce las "
|
||||||
|
"acciones, y ese daemon es lo único que tiene que estar en marcha."
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"<b>Tray and window</b> – with <i>Minimize to tray</i> the window is hidden "
|
||||||
|
"instead of closed when you close or minimize it; the tray icon shows it "
|
||||||
|
"again (<i>Show</i>) and quits the program (<i>Close</i>). Starting "
|
||||||
|
"gKeyMapper again closes the old window – rows that were not saved yet come "
|
||||||
|
"along."
|
||||||
|
msgstr ""
|
||||||
|
"<b>Bandeja y ventana</b> – con <i>Minimizar a la bandeja</i>, al cerrar o "
|
||||||
|
"minimizar la ventana se oculta en lugar de cerrarse; el icono de la bandeja "
|
||||||
|
"la vuelve a mostrar (<i>Mostrar</i>) y cierra el programa (<i>Cerrar</i>). "
|
||||||
|
"Volver a iniciar gKeyMapper cierra la ventana antigua: las filas aún sin "
|
||||||
|
"guardar se conservan."
|
||||||
|
|
||||||
|
msgid "Help"
|
||||||
|
msgstr "Ayuda"
|
||||||
|
|
||||||
|
msgid "Close window"
|
||||||
|
msgstr "Cerrar ventana"
|
||||||
|
|
||||||
|
msgid "Daemon: not reachable"
|
||||||
|
msgstr "Demonio: no accesible"
|
||||||
|
|
||||||
|
msgid "Daemon: running"
|
||||||
|
msgstr "Demonio: en ejecución"
|
||||||
|
|
||||||
|
msgid "Device: not found"
|
||||||
|
msgstr "Dispositivo: no encontrado"
|
||||||
|
|
||||||
|
#, c-format
|
||||||
|
msgid "Device: %s"
|
||||||
|
msgstr "Dispositivo: %s"
|
||||||
|
|
||||||
|
msgid "Configuration error"
|
||||||
|
msgstr "Error de configuración"
|
||||||
|
|
||||||
|
#, c-format
|
||||||
|
msgid "Last key: %s"
|
||||||
|
msgstr "Última tecla: %s"
|
||||||
|
|
||||||
|
msgid "Choose file"
|
||||||
|
msgstr "Elegir archivo"
|
||||||
|
|
||||||
|
msgid "_Cancel"
|
||||||
|
msgstr "_Cancelar"
|
||||||
|
|
||||||
|
msgid "_Open"
|
||||||
|
msgstr "_Abrir"
|
||||||
|
|
||||||
msgid "Choose file..."
|
msgid "Choose file..."
|
||||||
msgstr "Elegir archivo..."
|
msgstr "Elegir archivo..."
|
||||||
@@ -54,12 +163,12 @@ msgstr "p. ej. /usr/bin/firefox o comando personalizado"
|
|||||||
msgid "Capture"
|
msgid "Capture"
|
||||||
msgstr "Capturar"
|
msgstr "Capturar"
|
||||||
|
|
||||||
msgid "e.g. ctrl+c, ctrl+v"
|
|
||||||
msgstr "p. ej. ctrl+c, ctrl+v"
|
|
||||||
|
|
||||||
msgid "e.g. ctrl+w (held while the G key is held)"
|
msgid "e.g. ctrl+w (held while the G key is held)"
|
||||||
msgstr "p. ej. ctrl+w (mantenida mientras se mantiene pulsada la tecla G)"
|
msgstr "p. ej. ctrl+w (mantenida mientras se mantiene pulsada la tecla G)"
|
||||||
|
|
||||||
|
msgid "e.g. ctrl+c, ctrl+v"
|
||||||
|
msgstr "p. ej. ctrl+c, ctrl+v"
|
||||||
|
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Could not save %s.\n"
|
msgid "Could not save %s.\n"
|
||||||
msgstr "No se pudo guardar %s.\n"
|
msgstr "No se pudo guardar %s.\n"
|
||||||
@@ -67,19 +176,12 @@ msgstr "No se pudo guardar %s.\n"
|
|||||||
msgid "Mappings saved to "
|
msgid "Mappings saved to "
|
||||||
msgstr "Asignaciones guardadas en "
|
msgstr "Asignaciones guardadas en "
|
||||||
|
|
||||||
#, c-format
|
|
||||||
msgid "Could not open %s.\n"
|
|
||||||
msgstr "No se pudo abrir %s.\n"
|
|
||||||
|
|
||||||
msgid "Could not export mappings.\n"
|
msgid "Could not export mappings.\n"
|
||||||
msgstr "No se pudieron exportar las asignaciones.\n"
|
msgstr "No se pudieron exportar las asignaciones.\n"
|
||||||
|
|
||||||
msgid "gKeyMapper"
|
msgid "gKeyMapper"
|
||||||
msgstr "gKeyMapper"
|
msgstr "gKeyMapper"
|
||||||
|
|
||||||
msgid "G-Key mapper for the Logitech G910 (GTK4)."
|
|
||||||
msgstr "Asignador de teclas G para el Logitech G910 (GTK4)."
|
|
||||||
|
|
||||||
msgid "Export configuration"
|
msgid "Export configuration"
|
||||||
msgstr "Exportar configuración"
|
msgstr "Exportar configuración"
|
||||||
|
|
||||||
@@ -98,9 +200,15 @@ msgstr "Exportar configuración..."
|
|||||||
msgid "Import configuration..."
|
msgid "Import configuration..."
|
||||||
msgstr "Importar configuración..."
|
msgstr "Importar configuración..."
|
||||||
|
|
||||||
|
msgid "Options"
|
||||||
|
msgstr "Opciones"
|
||||||
|
|
||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "Acerca de"
|
msgstr "Acerca de"
|
||||||
|
|
||||||
|
msgid "Quit"
|
||||||
|
msgstr "Salir"
|
||||||
|
|
||||||
msgid "G1 to G9 of the Logitech G910 remapped to keys or commands"
|
msgid "G1 to G9 of the Logitech G910 remapped to keys or commands"
|
||||||
msgstr "Reasigna las G1 a G9 del Logitech G910 a teclas o comandos"
|
msgstr "Reasigna las G1 a G9 del Logitech G910 a teclas o comandos"
|
||||||
|
|
||||||
@@ -134,44 +242,12 @@ msgstr "Guardar asignaciones"
|
|||||||
msgid "Saved to ~/.config/gKeyMapper/mappings.conf"
|
msgid "Saved to ~/.config/gKeyMapper/mappings.conf"
|
||||||
msgstr "Guardado en ~/.config/gKeyMapper/mappings.conf"
|
msgstr "Guardado en ~/.config/gKeyMapper/mappings.conf"
|
||||||
|
|
||||||
msgid "Daemon: not reachable"
|
|
||||||
msgstr "Demonio: no accesible"
|
|
||||||
|
|
||||||
msgid "Daemon: running"
|
|
||||||
msgstr "Demonio: en ejecución"
|
|
||||||
|
|
||||||
msgid "Device: %s"
|
|
||||||
msgstr "Dispositivo: %s"
|
|
||||||
|
|
||||||
msgid "Device: not found"
|
|
||||||
msgstr "Dispositivo: no encontrado"
|
|
||||||
|
|
||||||
msgid "Configuration error"
|
|
||||||
msgstr "Error de configuración"
|
|
||||||
|
|
||||||
msgid "Last key: %s"
|
|
||||||
msgstr "Última tecla: %s"
|
|
||||||
|
|
||||||
msgid "Daemon: checking..."
|
msgid "Daemon: checking..."
|
||||||
msgstr "Demonio: comprobando..."
|
msgstr "Demonio: comprobando..."
|
||||||
|
|
||||||
msgid "Options"
|
#, c-format
|
||||||
msgstr "Opciones"
|
msgid "Could not open %s.\n"
|
||||||
|
msgstr "No se pudo abrir %s.\n"
|
||||||
msgid "Help"
|
|
||||||
msgstr "Ayuda"
|
|
||||||
|
|
||||||
msgid "Quit"
|
|
||||||
msgstr "Salir"
|
|
||||||
|
|
||||||
msgid "Show"
|
|
||||||
msgstr "Mostrar"
|
|
||||||
|
|
||||||
msgid "Close"
|
|
||||||
msgstr "Cerrar"
|
|
||||||
|
|
||||||
msgid "Close window"
|
|
||||||
msgstr "Cerrar ventana"
|
|
||||||
|
|
||||||
msgid "Window"
|
msgid "Window"
|
||||||
msgstr "Ventana"
|
msgstr "Ventana"
|
||||||
@@ -179,12 +255,16 @@ msgstr "Ventana"
|
|||||||
msgid "Minimize to tray"
|
msgid "Minimize to tray"
|
||||||
msgstr "Minimizar a la bandeja"
|
msgstr "Minimizar a la bandeja"
|
||||||
|
|
||||||
|
msgid "Start with the session"
|
||||||
|
msgstr "Iniciar con la sesión"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Closing or minimizing the window hides it instead of quitting. The tray icon "
|
"Opens this window minimized to the tray when you log in. Not needed for the "
|
||||||
"shows it again (Show) and quits the program (Close)."
|
"G keys - the daemon does the work and is the only part that has to run."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Al cerrar o minimizar, la ventana se oculta en lugar de salir. El icono de "
|
"Abre esta ventana minimizada en la bandeja al iniciar la sesión. No hace "
|
||||||
"la bandeja la vuelve a mostrar (Mostrar) y cierra el programa (Cerrar)."
|
"falta para las teclas G: el trabajo lo hace el daemon, y solo él tiene que "
|
||||||
|
"estar en marcha."
|
||||||
|
|
||||||
msgid "Language"
|
msgid "Language"
|
||||||
msgstr "Idioma"
|
msgstr "Idioma"
|
||||||
@@ -195,20 +275,6 @@ msgstr "Idioma del sistema"
|
|||||||
msgid "The interface switches right away, including the tray menu."
|
msgid "The interface switches right away, including the tray menu."
|
||||||
msgstr "La interfaz cambia al instante, incluido el menú de la bandeja."
|
msgstr "La interfaz cambia al instante, incluido el menú de la bandeja."
|
||||||
|
|
||||||
msgid "Start with the session"
|
|
||||||
msgstr "Iniciar con la sesión"
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"Opens this window when you log in. Not needed for the G keys - the daemon "
|
|
||||||
"does the work and is the only part that has to run."
|
|
||||||
msgstr ""
|
|
||||||
"Abre esta ventana al iniciar la sesión. No hace falta para las teclas G: el "
|
|
||||||
"trabajo lo hace el daemon, y solo él tiene que estar en marcha."
|
|
||||||
|
|
||||||
msgid "No tray is available on this desktop, so the window cannot be hidden."
|
|
||||||
msgstr ""
|
|
||||||
"Este escritorio no tiene bandeja, así que la ventana no se puede ocultar."
|
|
||||||
|
|
||||||
msgid "Daemon status"
|
msgid "Daemon status"
|
||||||
msgstr "Estado del daemon"
|
msgstr "Estado del daemon"
|
||||||
|
|
||||||
@@ -230,6 +296,17 @@ msgstr "Archivo de configuración"
|
|||||||
msgid "Last key"
|
msgid "Last key"
|
||||||
msgstr "Última tecla"
|
msgstr "Última tecla"
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"Closing or minimizing the window hides it instead of quitting. The tray icon "
|
||||||
|
"shows it again (Show) and quits the program (Close)."
|
||||||
|
msgstr ""
|
||||||
|
"Al cerrar o minimizar, la ventana se oculta en lugar de salir. El icono de "
|
||||||
|
"la bandeja la vuelve a mostrar (Mostrar) y cierra el programa (Cerrar)."
|
||||||
|
|
||||||
|
msgid "No tray is available on this desktop, so the window cannot be hidden."
|
||||||
|
msgstr ""
|
||||||
|
"Este escritorio no tiene bandeja, así que la ventana no se puede ocultar."
|
||||||
|
|
||||||
msgid "active"
|
msgid "active"
|
||||||
msgstr "activo"
|
msgstr "activo"
|
||||||
|
|
||||||
@@ -245,82 +322,8 @@ msgstr "no accesible"
|
|||||||
msgid "not found"
|
msgid "not found"
|
||||||
msgstr "no encontrado"
|
msgstr "no encontrado"
|
||||||
|
|
||||||
msgid ""
|
msgid "Show"
|
||||||
"<b>The nine rows</b> – one per G key. Choose the type, then enter or capture "
|
msgstr "Mostrar"
|
||||||
"the target and press <i>Save mappings</i>."
|
|
||||||
msgstr ""
|
|
||||||
"<b>Las nueve filas</b> – una por tecla G. Elige el tipo, introduce o captura "
|
|
||||||
"el destino y pulsa <i>Guardar asignaciones</i>."
|
|
||||||
|
|
||||||
msgid "<b>Key</b> – presses the captured sequence once, e.g. ctrl+c."
|
msgid "Close"
|
||||||
msgstr "<b>Tecla</b> – pulsa la secuencia capturada una vez, p. ej. ctrl+c."
|
msgstr "Cerrar"
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"<b>Key (hold)</b> – presses it and keeps it down while the G key is held, "
|
|
||||||
"e.g. for push-to-talk. Only a single step can be held."
|
|
||||||
msgstr ""
|
|
||||||
"<b>Tecla (mantener)</b> – la pulsa y la mantiene mientras se mantiene la "
|
|
||||||
"tecla G, p. ej. para pulsar para hablar. Solo se puede mantener un paso."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"<b>Command</b> – runs a program or a command line, e.g. /usr/bin/firefox "
|
|
||||||
"--new-window."
|
|
||||||
msgstr ""
|
|
||||||
"<b>Comando</b> – ejecuta un programa o una línea de comandos, p. ej. "
|
|
||||||
"/usr/bin/firefox --new-window."
|
|
||||||
|
|
||||||
msgid "<b>Disabled</b> – the key does nothing."
|
|
||||||
msgstr "<b>Desactivado</b> – la tecla no hace nada."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"<b>Capture</b> – records the keys you press next: press them, then "
|
|
||||||
"<i>Done</i>. Modifiers are kept (shift, altgr, …) and the names are layout "
|
|
||||||
"independent, so ctrl+z means the key that types z on your layout."
|
|
||||||
msgstr ""
|
|
||||||
"<b>Capturar</b> – graba las siguientes teclas que pulses: púlsalas y luego "
|
|
||||||
"<i>Aceptar</i>. Los modificadores se conservan (shift, altgr, …) y los "
|
|
||||||
"nombres no dependen de la distribución, así que ctrl+z es la tecla que "
|
|
||||||
"escribe z en tu distribución."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"<b>Save mappings</b> – writes ~/.config/gKeyMapper/mappings.conf and makes "
|
|
||||||
"the daemon reload it. The menu entries <i>Export configuration...</i> and "
|
|
||||||
"<i>Import configuration...</i> do the same with a file you choose."
|
|
||||||
msgstr ""
|
|
||||||
"<b>Guardar asignaciones</b> – escribe ~/.config/gKeyMapper/mappings.conf y "
|
|
||||||
"hace que el daemon la recargue. Las entradas de menú <i>Exportar "
|
|
||||||
"configuración...</i> e <i>Importar configuración...</i> hacen lo mismo con "
|
|
||||||
"el archivo que elijas."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"<b>Daemon status</b> – the footer shows whether gkeymapper-daemon is "
|
|
||||||
"running, which keyboard it found and the last G key that fired. "
|
|
||||||
"<i>Options</i> shows the same in more detail."
|
|
||||||
msgstr ""
|
|
||||||
"<b>Estado del daemon</b> – el pie de ventana muestra si gkeymapper-daemon "
|
|
||||||
"está en ejecución, qué teclado ha encontrado y la última tecla G que se ha "
|
|
||||||
"pulsado. <i>Opciones</i> muestra lo mismo con más detalle."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"<b>Autostart</b> – <i>Start with the session</i> only opens this window when "
|
|
||||||
"you log in. The G keys work without it: gkeymapper-daemon (a user service) "
|
|
||||||
"reads them and replays the actions, and that daemon is the only part that has "
|
|
||||||
"to run."
|
|
||||||
msgstr ""
|
|
||||||
"<b>Inicio automático</b> – <i>Iniciar con la sesión</i> solo abre esta "
|
|
||||||
"ventana. Las teclas G funcionan sin ella: gkeymapper-daemon (un servicio de "
|
|
||||||
"usuario) las lee y reproduce las acciones, y ese daemon es lo único que tiene "
|
|
||||||
"que estar en marcha."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"<b>Tray and window</b> – with <i>Minimize to tray</i> the window is hidden "
|
|
||||||
"instead of closed when you close or minimize it; the tray icon shows it "
|
|
||||||
"again (<i>Show</i>) and quits the program (<i>Close</i>). Starting "
|
|
||||||
"gKeyMapper again closes the old window – rows that were not saved are lost "
|
|
||||||
"with it."
|
|
||||||
msgstr ""
|
|
||||||
"<b>Bandeja y ventana</b> – con <i>Minimizar a la bandeja</i>, al cerrar o "
|
|
||||||
"minimizar la ventana se oculta en lugar de cerrarse; el icono de la bandeja "
|
|
||||||
"la vuelve a mostrar (<i>Mostrar</i>) y cierra el programa (<i>Cerrar</i>). "
|
|
||||||
"Volver a iniciar gKeyMapper cierra la ventana antigua: las filas no "
|
|
||||||
"guardadas se pierden con ella."
|
|
||||||
|
|||||||
326
po/fr.po
326
po/fr.po
@@ -4,7 +4,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: gkeymapper 0.1\n"
|
"Project-Id-Version: gkeymapper 0.1\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-18 03:08+0200\n"
|
"POT-Creation-Date: 2026-10-18 20:18+0200\n"
|
||||||
"PO-Revision-Date: 2026-10-18 03:08+0200\n"
|
"PO-Revision-Date: 2026-10-18 03:08+0200\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: French\n"
|
"Language-Team: French\n"
|
||||||
@@ -13,14 +13,8 @@ msgstr ""
|
|||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
msgid "Choose file"
|
msgid "G-Key mapper for the Logitech G910 (GTK4)."
|
||||||
msgstr "Choisir un fichier"
|
msgstr "Assignateur de touches G pour le Logitech G910 (GTK4)."
|
||||||
|
|
||||||
msgid "_Cancel"
|
|
||||||
msgstr "_Annuler"
|
|
||||||
|
|
||||||
msgid "_Open"
|
|
||||||
msgstr "_Ouvrir"
|
|
||||||
|
|
||||||
msgid "No keys recorded yet"
|
msgid "No keys recorded yet"
|
||||||
msgstr "Aucune touche enregistrée pour le moment"
|
msgstr "Aucune touche enregistrée pour le moment"
|
||||||
@@ -28,6 +22,14 @@ msgstr "Aucune touche enregistrée pour le moment"
|
|||||||
msgid "Capture key sequence"
|
msgid "Capture key sequence"
|
||||||
msgstr "Capturer une séquence de touches"
|
msgstr "Capturer une séquence de touches"
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"Press keys in order (modifiers like Ctrl, Alt, Shift, Super and AltGr are "
|
||||||
|
"recorded too). Click Done to apply or Cancel to discard."
|
||||||
|
msgstr ""
|
||||||
|
"Appuyez sur les touches dans l'ordre (les modificateurs comme Ctrl, Alt, "
|
||||||
|
"Maj, Super et AltGr sont également enregistrés). Cliquez sur Terminer pour "
|
||||||
|
"appliquer ou sur Annuler pour abandonner."
|
||||||
|
|
||||||
msgid "Clear"
|
msgid "Clear"
|
||||||
msgstr "Effacer"
|
msgstr "Effacer"
|
||||||
|
|
||||||
@@ -38,12 +40,122 @@ msgid "Done"
|
|||||||
msgstr "Terminer"
|
msgstr "Terminer"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Press keys in order (modifiers like Ctrl, Alt, Shift, Super and AltGr are "
|
"<b>The nine rows</b> – one per G key. Choose the type, then enter or capture "
|
||||||
"recorded too). Click Done to apply or Cancel to discard."
|
"the target and press <i>Save mappings</i>."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Appuyez sur les touches dans l'ordre (les modificateurs comme Ctrl, Alt, "
|
"<b>Les neuf lignes</b> – une par touche G. Choisissez le type, saisissez ou "
|
||||||
"Maj, Super et AltGr sont également enregistrés). Cliquez sur Terminer pour "
|
"capturez la cible puis cliquez sur <i>Enregistrer les attributions</i>."
|
||||||
"appliquer ou sur Annuler pour abandonner."
|
|
||||||
|
msgid "<b>Key</b> – presses the captured sequence once, e.g. ctrl+c."
|
||||||
|
msgstr "<b>Touche</b> – presse une fois la séquence capturée, par ex. ctrl+c."
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"<b>Key (hold)</b> – presses it and keeps it down while the G key is held, e."
|
||||||
|
"g. for push-to-talk. Only a single step can be held."
|
||||||
|
msgstr ""
|
||||||
|
"<b>Touche (maintenue)</b> – l'appuie et la maintient tant que la touche G "
|
||||||
|
"est maintenue, p. ex. pour appuyer-pour-parler. Une seule étape peut être "
|
||||||
|
"maintenue."
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"<b>Command</b> – runs a program or a command line, e.g. /usr/bin/firefox --"
|
||||||
|
"new-window."
|
||||||
|
msgstr ""
|
||||||
|
"<b>Commande</b> – lance un programme ou une ligne de commande, par ex. /usr/"
|
||||||
|
"bin/firefox --new-window."
|
||||||
|
|
||||||
|
msgid "<b>Disabled</b> – the key does nothing."
|
||||||
|
msgstr "<b>Désactivé</b> – la touche ne fait rien."
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"<b>Capture</b> – records the keys you press next: press them, then <i>Done</"
|
||||||
|
"i>. Modifiers are kept (shift, altgr, …) and the names are layout "
|
||||||
|
"independent, so ctrl+z means the key that types z on your layout."
|
||||||
|
msgstr ""
|
||||||
|
"<b>Capturer</b> – enregistre les touches que vous pressez ensuite : pressez-"
|
||||||
|
"les puis <i>Terminer</i>. Les modificateurs sont conservés (shift, altgr, …) "
|
||||||
|
"et les noms ne dépendent pas de la disposition, donc ctrl+z désigne la "
|
||||||
|
"touche qui tape z sur votre disposition."
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"<b>Save mappings</b> – writes ~/.config/gKeyMapper/mappings.conf and makes "
|
||||||
|
"the daemon reload it. The menu entries <i>Export configuration...</i> and "
|
||||||
|
"<i>Import configuration...</i> do the same with a file you choose."
|
||||||
|
msgstr ""
|
||||||
|
"<b>Enregistrer les attributions</b> – écrit ~/.config/gKeyMapper/mappings."
|
||||||
|
"conf et fait recharger le fichier au démon. Les entrées de menu <i>Exporter "
|
||||||
|
"la configuration...</i> et <i>Importer la configuration...</i> font la même "
|
||||||
|
"chose avec le fichier de votre choix."
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"<b>Daemon status</b> – the footer shows whether gkeymapper-daemon is "
|
||||||
|
"running, which keyboard it found and the last G key that fired. <i>Options</"
|
||||||
|
"i> shows the same in more detail."
|
||||||
|
msgstr ""
|
||||||
|
"<b>État du démon</b> – le pied de fenêtre indique si gkeymapper-daemon est "
|
||||||
|
"en cours d'exécution, quel clavier a été trouvé et la dernière touche G "
|
||||||
|
"déclenchée. <i>Options</i> affiche la même chose plus en détail."
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"<b>Autostart</b> – <i>Start with the session</i> opens this window minimized "
|
||||||
|
"to the tray when you log in. The G keys work without it: gkeymapper-daemon "
|
||||||
|
"(a user service) reads them and replays the actions, and that daemon is the "
|
||||||
|
"only part that has to run."
|
||||||
|
msgstr ""
|
||||||
|
"<b>Démarrage automatique</b> – <i>Démarrer avec la session</i> ouvre cette "
|
||||||
|
"fenêtre réduite dans la zone de notification à l'ouverture de la session. "
|
||||||
|
"Les touches G fonctionnent sans elle : gkeymapper-daemon (un service "
|
||||||
|
"utilisateur) les lit et rejoue les actions, et ce démon est la seule partie "
|
||||||
|
"qui doit tourner."
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"<b>Tray and window</b> – with <i>Minimize to tray</i> the window is hidden "
|
||||||
|
"instead of closed when you close or minimize it; the tray icon shows it "
|
||||||
|
"again (<i>Show</i>) and quits the program (<i>Close</i>). Starting "
|
||||||
|
"gKeyMapper again closes the old window – rows that were not saved yet come "
|
||||||
|
"along."
|
||||||
|
msgstr ""
|
||||||
|
"<b>Zone de notification et fenêtre</b> – avec <i>Réduire dans la zone de "
|
||||||
|
"notification</i>, la fenêtre est masquée au lieu d'être fermée lorsque vous "
|
||||||
|
"la fermez ou la réduisez ; l'icône de la zone de notification la réaffiche "
|
||||||
|
"(<i>Afficher</i>) et quitte le programme (<i>Fermer</i>). Relancer "
|
||||||
|
"gKeyMapper ferme l'ancienne fenêtre : les lignes pas encore enregistrées "
|
||||||
|
"sont conservées."
|
||||||
|
|
||||||
|
msgid "Help"
|
||||||
|
msgstr "Aide"
|
||||||
|
|
||||||
|
msgid "Close window"
|
||||||
|
msgstr "Fermer la fenêtre"
|
||||||
|
|
||||||
|
msgid "Daemon: not reachable"
|
||||||
|
msgstr "Démon : injoignable"
|
||||||
|
|
||||||
|
msgid "Daemon: running"
|
||||||
|
msgstr "Démon : en cours d'exécution"
|
||||||
|
|
||||||
|
msgid "Device: not found"
|
||||||
|
msgstr "Appareil : introuvable"
|
||||||
|
|
||||||
|
#, c-format
|
||||||
|
msgid "Device: %s"
|
||||||
|
msgstr "Appareil : %s"
|
||||||
|
|
||||||
|
msgid "Configuration error"
|
||||||
|
msgstr "Erreur de configuration"
|
||||||
|
|
||||||
|
#, c-format
|
||||||
|
msgid "Last key: %s"
|
||||||
|
msgstr "Dernière touche : %s"
|
||||||
|
|
||||||
|
msgid "Choose file"
|
||||||
|
msgstr "Choisir un fichier"
|
||||||
|
|
||||||
|
msgid "_Cancel"
|
||||||
|
msgstr "_Annuler"
|
||||||
|
|
||||||
|
msgid "_Open"
|
||||||
|
msgstr "_Ouvrir"
|
||||||
|
|
||||||
msgid "Choose file..."
|
msgid "Choose file..."
|
||||||
msgstr "Choisir un fichier..."
|
msgstr "Choisir un fichier..."
|
||||||
@@ -54,12 +166,12 @@ msgstr "ex. /usr/bin/firefox ou commande personnalisée"
|
|||||||
msgid "Capture"
|
msgid "Capture"
|
||||||
msgstr "Capturer"
|
msgstr "Capturer"
|
||||||
|
|
||||||
msgid "e.g. ctrl+c, ctrl+v"
|
|
||||||
msgstr "ex. ctrl+c, ctrl+v"
|
|
||||||
|
|
||||||
msgid "e.g. ctrl+w (held while the G key is held)"
|
msgid "e.g. ctrl+w (held while the G key is held)"
|
||||||
msgstr "ex. ctrl+w (maintenue tant que la touche G est enfoncée)"
|
msgstr "ex. ctrl+w (maintenue tant que la touche G est enfoncée)"
|
||||||
|
|
||||||
|
msgid "e.g. ctrl+c, ctrl+v"
|
||||||
|
msgstr "ex. ctrl+c, ctrl+v"
|
||||||
|
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Could not save %s.\n"
|
msgid "Could not save %s.\n"
|
||||||
msgstr "Impossible d'enregistrer %s.\n"
|
msgstr "Impossible d'enregistrer %s.\n"
|
||||||
@@ -67,19 +179,12 @@ msgstr "Impossible d'enregistrer %s.\n"
|
|||||||
msgid "Mappings saved to "
|
msgid "Mappings saved to "
|
||||||
msgstr "Attributions enregistrées dans "
|
msgstr "Attributions enregistrées dans "
|
||||||
|
|
||||||
#, c-format
|
|
||||||
msgid "Could not open %s.\n"
|
|
||||||
msgstr "Impossible d'ouvrir %s.\n"
|
|
||||||
|
|
||||||
msgid "Could not export mappings.\n"
|
msgid "Could not export mappings.\n"
|
||||||
msgstr "Impossible d'exporter les attributions.\n"
|
msgstr "Impossible d'exporter les attributions.\n"
|
||||||
|
|
||||||
msgid "gKeyMapper"
|
msgid "gKeyMapper"
|
||||||
msgstr "gKeyMapper"
|
msgstr "gKeyMapper"
|
||||||
|
|
||||||
msgid "G-Key mapper for the Logitech G910 (GTK4)."
|
|
||||||
msgstr "Assignateur de touches G pour le Logitech G910 (GTK4)."
|
|
||||||
|
|
||||||
msgid "Export configuration"
|
msgid "Export configuration"
|
||||||
msgstr "Exporter la configuration"
|
msgstr "Exporter la configuration"
|
||||||
|
|
||||||
@@ -98,11 +203,19 @@ msgstr "Exporter la configuration..."
|
|||||||
msgid "Import configuration..."
|
msgid "Import configuration..."
|
||||||
msgstr "Importer la configuration..."
|
msgstr "Importer la configuration..."
|
||||||
|
|
||||||
|
msgid "Options"
|
||||||
|
msgstr "Options"
|
||||||
|
|
||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "À propos"
|
msgstr "À propos"
|
||||||
|
|
||||||
|
msgid "Quit"
|
||||||
|
msgstr "Quitter"
|
||||||
|
|
||||||
msgid "G1 to G9 of the Logitech G910 remapped to keys or commands"
|
msgid "G1 to G9 of the Logitech G910 remapped to keys or commands"
|
||||||
msgstr "Réassignation des touches G1 à G9 du Logitech G910 vers des touches ou des commandes"
|
msgstr ""
|
||||||
|
"Réassignation des touches G1 à G9 du Logitech G910 vers des touches ou des "
|
||||||
|
"commandes"
|
||||||
|
|
||||||
msgid "G-Key"
|
msgid "G-Key"
|
||||||
msgstr "Touche G"
|
msgstr "Touche G"
|
||||||
@@ -134,44 +247,12 @@ msgstr "Enregistrer les attributions"
|
|||||||
msgid "Saved to ~/.config/gKeyMapper/mappings.conf"
|
msgid "Saved to ~/.config/gKeyMapper/mappings.conf"
|
||||||
msgstr "Enregistré dans ~/.config/gKeyMapper/mappings.conf"
|
msgstr "Enregistré dans ~/.config/gKeyMapper/mappings.conf"
|
||||||
|
|
||||||
msgid "Daemon: not reachable"
|
|
||||||
msgstr "Démon : injoignable"
|
|
||||||
|
|
||||||
msgid "Daemon: running"
|
|
||||||
msgstr "Démon : en cours d'exécution"
|
|
||||||
|
|
||||||
msgid "Device: %s"
|
|
||||||
msgstr "Appareil : %s"
|
|
||||||
|
|
||||||
msgid "Device: not found"
|
|
||||||
msgstr "Appareil : introuvable"
|
|
||||||
|
|
||||||
msgid "Configuration error"
|
|
||||||
msgstr "Erreur de configuration"
|
|
||||||
|
|
||||||
msgid "Last key: %s"
|
|
||||||
msgstr "Dernière touche : %s"
|
|
||||||
|
|
||||||
msgid "Daemon: checking..."
|
msgid "Daemon: checking..."
|
||||||
msgstr "Démon : vérification..."
|
msgstr "Démon : vérification..."
|
||||||
|
|
||||||
msgid "Options"
|
#, c-format
|
||||||
msgstr "Options"
|
msgid "Could not open %s.\n"
|
||||||
|
msgstr "Impossible d'ouvrir %s.\n"
|
||||||
msgid "Help"
|
|
||||||
msgstr "Aide"
|
|
||||||
|
|
||||||
msgid "Quit"
|
|
||||||
msgstr "Quitter"
|
|
||||||
|
|
||||||
msgid "Show"
|
|
||||||
msgstr "Afficher"
|
|
||||||
|
|
||||||
msgid "Close"
|
|
||||||
msgstr "Fermer"
|
|
||||||
|
|
||||||
msgid "Close window"
|
|
||||||
msgstr "Fermer la fenêtre"
|
|
||||||
|
|
||||||
msgid "Window"
|
msgid "Window"
|
||||||
msgstr "Fenêtre"
|
msgstr "Fenêtre"
|
||||||
@@ -179,13 +260,16 @@ msgstr "Fenêtre"
|
|||||||
msgid "Minimize to tray"
|
msgid "Minimize to tray"
|
||||||
msgstr "Réduire dans la zone de notification"
|
msgstr "Réduire dans la zone de notification"
|
||||||
|
|
||||||
|
msgid "Start with the session"
|
||||||
|
msgstr "Démarrer avec la session"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Closing or minimizing the window hides it instead of quitting. The tray icon "
|
"Opens this window minimized to the tray when you log in. Not needed for the "
|
||||||
"shows it again (Show) and quits the program (Close)."
|
"G keys - the daemon does the work and is the only part that has to run."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"À la fermeture ou à la réduction, la fenêtre est masquée au lieu de quitter. "
|
"Ouvre cette fenêtre réduite dans la zone de notification à l'ouverture de la "
|
||||||
"L'icône de la zone de notification la réaffiche (Afficher) et quitte le "
|
"session. Inutile pour les touches G : c'est le démon qui fait le travail, et "
|
||||||
"programme (Fermer)."
|
"lui seul doit tourner."
|
||||||
|
|
||||||
msgid "Language"
|
msgid "Language"
|
||||||
msgstr "Langue"
|
msgstr "Langue"
|
||||||
@@ -198,21 +282,6 @@ msgstr ""
|
|||||||
"L'interface change immédiatement, y compris le menu de la zone de "
|
"L'interface change immédiatement, y compris le menu de la zone de "
|
||||||
"notification."
|
"notification."
|
||||||
|
|
||||||
msgid "Start with the session"
|
|
||||||
msgstr "Démarrer avec la session"
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"Opens this window when you log in. Not needed for the G keys - the daemon "
|
|
||||||
"does the work and is the only part that has to run."
|
|
||||||
msgstr ""
|
|
||||||
"Ouvre cette fenêtre à l'ouverture de la session. Inutile pour les touches G : "
|
|
||||||
"c'est le démon qui fait le travail, et lui seul doit tourner."
|
|
||||||
|
|
||||||
msgid "No tray is available on this desktop, so the window cannot be hidden."
|
|
||||||
msgstr ""
|
|
||||||
"Ce bureau n'a pas de zone de notification, la fenêtre ne peut donc pas être "
|
|
||||||
"masquée."
|
|
||||||
|
|
||||||
msgid "Daemon status"
|
msgid "Daemon status"
|
||||||
msgstr "État du démon"
|
msgstr "État du démon"
|
||||||
|
|
||||||
@@ -234,6 +303,19 @@ msgstr "Fichier de configuration"
|
|||||||
msgid "Last key"
|
msgid "Last key"
|
||||||
msgstr "Dernière touche"
|
msgstr "Dernière touche"
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"Closing or minimizing the window hides it instead of quitting. The tray icon "
|
||||||
|
"shows it again (Show) and quits the program (Close)."
|
||||||
|
msgstr ""
|
||||||
|
"À la fermeture ou à la réduction, la fenêtre est masquée au lieu de quitter. "
|
||||||
|
"L'icône de la zone de notification la réaffiche (Afficher) et quitte le "
|
||||||
|
"programme (Fermer)."
|
||||||
|
|
||||||
|
msgid "No tray is available on this desktop, so the window cannot be hidden."
|
||||||
|
msgstr ""
|
||||||
|
"Ce bureau n'a pas de zone de notification, la fenêtre ne peut donc pas être "
|
||||||
|
"masquée."
|
||||||
|
|
||||||
msgid "active"
|
msgid "active"
|
||||||
msgstr "actif"
|
msgstr "actif"
|
||||||
|
|
||||||
@@ -249,84 +331,8 @@ msgstr "inaccessible"
|
|||||||
msgid "not found"
|
msgid "not found"
|
||||||
msgstr "introuvable"
|
msgstr "introuvable"
|
||||||
|
|
||||||
msgid ""
|
msgid "Show"
|
||||||
"<b>The nine rows</b> – one per G key. Choose the type, then enter or capture "
|
msgstr "Afficher"
|
||||||
"the target and press <i>Save mappings</i>."
|
|
||||||
msgstr ""
|
|
||||||
"<b>Les neuf lignes</b> – une par touche G. Choisissez le type, saisissez ou "
|
|
||||||
"capturez la cible puis cliquez sur <i>Enregistrer les attributions</i>."
|
|
||||||
|
|
||||||
msgid "<b>Key</b> – presses the captured sequence once, e.g. ctrl+c."
|
msgid "Close"
|
||||||
msgstr "<b>Touche</b> – presse une fois la séquence capturée, par ex. ctrl+c."
|
msgstr "Fermer"
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"<b>Key (hold)</b> – presses it and keeps it down while the G key is held, "
|
|
||||||
"e.g. for push-to-talk. Only a single step can be held."
|
|
||||||
msgstr ""
|
|
||||||
"<b>Touche (maintenue)</b> – l'appuie et la maintient tant que la touche G "
|
|
||||||
"est maintenue, p. ex. pour appuyer-pour-parler. Une seule étape peut être "
|
|
||||||
"maintenue."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"<b>Command</b> – runs a program or a command line, e.g. /usr/bin/firefox "
|
|
||||||
"--new-window."
|
|
||||||
msgstr ""
|
|
||||||
"<b>Commande</b> – lance un programme ou une ligne de commande, par ex. "
|
|
||||||
"/usr/bin/firefox --new-window."
|
|
||||||
|
|
||||||
msgid "<b>Disabled</b> – the key does nothing."
|
|
||||||
msgstr "<b>Désactivé</b> – la touche ne fait rien."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"<b>Capture</b> – records the keys you press next: press them, then "
|
|
||||||
"<i>Done</i>. Modifiers are kept (shift, altgr, …) and the names are layout "
|
|
||||||
"independent, so ctrl+z means the key that types z on your layout."
|
|
||||||
msgstr ""
|
|
||||||
"<b>Capturer</b> – enregistre les touches que vous pressez ensuite : "
|
|
||||||
"pressez-les puis <i>Terminer</i>. Les modificateurs sont conservés (shift, "
|
|
||||||
"altgr, …) et les noms ne dépendent pas de la disposition, donc ctrl+z "
|
|
||||||
"désigne la touche qui tape z sur votre disposition."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"<b>Save mappings</b> – writes ~/.config/gKeyMapper/mappings.conf and makes "
|
|
||||||
"the daemon reload it. The menu entries <i>Export configuration...</i> and "
|
|
||||||
"<i>Import configuration...</i> do the same with a file you choose."
|
|
||||||
msgstr ""
|
|
||||||
"<b>Enregistrer les attributions</b> – écrit "
|
|
||||||
"~/.config/gKeyMapper/mappings.conf et fait recharger le fichier au démon. "
|
|
||||||
"Les entrées de menu <i>Exporter la configuration...</i> et <i>Importer la "
|
|
||||||
"configuration...</i> font la même chose avec le fichier de votre choix."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"<b>Daemon status</b> – the footer shows whether gkeymapper-daemon is "
|
|
||||||
"running, which keyboard it found and the last G key that fired. "
|
|
||||||
"<i>Options</i> shows the same in more detail."
|
|
||||||
msgstr ""
|
|
||||||
"<b>État du démon</b> – le pied de fenêtre indique si gkeymapper-daemon est "
|
|
||||||
"en cours d'exécution, quel clavier a été trouvé et la dernière touche G "
|
|
||||||
"déclenchée. <i>Options</i> affiche la même chose plus en détail."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"<b>Autostart</b> – <i>Start with the session</i> only opens this window when "
|
|
||||||
"you log in. The G keys work without it: gkeymapper-daemon (a user service) "
|
|
||||||
"reads them and replays the actions, and that daemon is the only part that has "
|
|
||||||
"to run."
|
|
||||||
msgstr ""
|
|
||||||
"<b>Démarrage automatique</b> – <i>Démarrer avec la session</i> ouvre "
|
|
||||||
"seulement cette fenêtre. Les touches G fonctionnent sans elle : "
|
|
||||||
"gkeymapper-daemon (un service utilisateur) les lit et rejoue les actions, et "
|
|
||||||
"ce démon est la seule partie qui doit tourner."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"<b>Tray and window</b> – with <i>Minimize to tray</i> the window is hidden "
|
|
||||||
"instead of closed when you close or minimize it; the tray icon shows it "
|
|
||||||
"again (<i>Show</i>) and quits the program (<i>Close</i>). Starting "
|
|
||||||
"gKeyMapper again closes the old window – rows that were not saved are lost "
|
|
||||||
"with it."
|
|
||||||
msgstr ""
|
|
||||||
"<b>Zone de notification et fenêtre</b> – avec <i>Réduire dans la zone de "
|
|
||||||
"notification</i>, la fenêtre est masquée au lieu d'être fermée lorsque vous "
|
|
||||||
"la fermez ou la réduisez ; l'icône de la zone de notification la réaffiche "
|
|
||||||
"(<i>Afficher</i>) et quitte le programme (<i>Fermer</i>). Relancer "
|
|
||||||
"gKeyMapper ferme l'ancienne fenêtre : les lignes non enregistrées sont "
|
|
||||||
"perdues avec elle."
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-10-18 03:08+0200\n"
|
"POT-Creation-Date: 2026-10-18 20:18+0200\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@@ -17,16 +17,8 @@ msgstr ""
|
|||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
#: src/main.cpp:126
|
#: src/autostart.cpp:97 src/main.cpp:280 src/trayicon.cpp:371
|
||||||
msgid "Choose file"
|
msgid "G-Key mapper for the Logitech G910 (GTK4)."
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:129 src/main.cpp:300 src/main.cpp:312
|
|
||||||
msgid "_Cancel"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:130 src/main.cpp:313
|
|
||||||
msgid "_Open"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/capturedialog.cpp:115 src/capturedialog.cpp:226
|
#: src/capturedialog.cpp:115 src/capturedialog.cpp:226
|
||||||
@@ -37,6 +29,12 @@ msgstr ""
|
|||||||
msgid "Capture key sequence"
|
msgid "Capture key sequence"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/capturedialog.cpp:220
|
||||||
|
msgid ""
|
||||||
|
"Press keys in order (modifiers like Ctrl, Alt, Shift, Super and AltGr are "
|
||||||
|
"recorded too). Click Done to apply or Cancel to discard."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/capturedialog.cpp:250
|
#: src/capturedialog.cpp:250
|
||||||
msgid "Clear"
|
msgid "Clear"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -49,270 +47,6 @@ msgstr ""
|
|||||||
msgid "Done"
|
msgid "Done"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/capturedialog.cpp:220
|
|
||||||
msgid ""
|
|
||||||
"Press keys in order (modifiers like Ctrl, Alt, Shift, Super and AltGr are "
|
|
||||||
"recorded too). Click Done to apply or Cancel to discard."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:160
|
|
||||||
msgid "Choose file..."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:162
|
|
||||||
msgid "e.g. /usr/bin/firefox or custom command"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:164 src/main.cpp:168 src/main.cpp:685
|
|
||||||
msgid "Capture"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:170 src/main.cpp:678
|
|
||||||
msgid "e.g. ctrl+c, ctrl+v"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:166
|
|
||||||
msgid "e.g. ctrl+w (held while the G key is held)"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:185
|
|
||||||
#, c-format
|
|
||||||
msgid "Could not save %s.\n"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:193
|
|
||||||
msgid "Mappings saved to "
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/mappings.cpp:41
|
|
||||||
#, c-format
|
|
||||||
msgid "Could not open %s.\n"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:203
|
|
||||||
msgid "Could not export mappings.\n"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:264 src/main.cpp:590 src/trayicon.cpp:370 src/trayicon.cpp:500
|
|
||||||
msgid "gKeyMapper"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/autostart.cpp:97 src/main.cpp:267 src/trayicon.cpp:371
|
|
||||||
msgid "G-Key mapper for the Logitech G910 (GTK4)."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:297
|
|
||||||
msgid "Export configuration"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:301
|
|
||||||
msgid "_Save"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:309
|
|
||||||
msgid "Import configuration"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:575
|
|
||||||
msgid "gKeyMapper – Logitech G910"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:594
|
|
||||||
msgid "Export configuration..."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:595
|
|
||||||
msgid "Import configuration..."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:603
|
|
||||||
msgid "About"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:623
|
|
||||||
msgid "G1 to G9 of the Logitech G910 remapped to keys or commands"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:644
|
|
||||||
msgid "G-Key"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:644
|
|
||||||
msgid "Type"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:644
|
|
||||||
msgid "Target (Key/Command)"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:644
|
|
||||||
msgid "Action"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:653
|
|
||||||
msgid "Key"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:653
|
|
||||||
msgid "Command"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:653
|
|
||||||
msgid "Disabled"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:654
|
|
||||||
msgid "Key (hold)"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:713
|
|
||||||
msgid "Save mappings"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:720
|
|
||||||
msgid "Saved to ~/.config/gKeyMapper/mappings.conf"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:63
|
|
||||||
msgid "Daemon: not reachable"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:65
|
|
||||||
msgid "Daemon: running"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:70
|
|
||||||
msgid "Device: not found"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:73
|
|
||||||
msgid "Device: %s"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:78 src/optionsdialog.cpp:212
|
|
||||||
msgid "Configuration error"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:83
|
|
||||||
msgid "Last key: %s"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:731
|
|
||||||
msgid "Daemon: checking..."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:598 src/optionsdialog.cpp:81
|
|
||||||
msgid "Options"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/help.cpp:67 src/help.cpp:86 src/main.cpp:602
|
|
||||||
msgid "Help"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/main.cpp:609
|
|
||||||
msgid "Quit"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/trayicon.cpp:136
|
|
||||||
msgid "Show"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/trayicon.cpp:139
|
|
||||||
msgid "Close"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/help.cpp:95 src/optionsdialog.cpp:164
|
|
||||||
msgid "Close window"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/optionsdialog.cpp:95
|
|
||||||
msgid "Window"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/optionsdialog.cpp:97
|
|
||||||
msgid "Minimize to tray"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/optionsdialog.cpp:200
|
|
||||||
msgid ""
|
|
||||||
"Closing or minimizing the window hides it instead of quitting. The tray icon "
|
|
||||||
"shows it again (Show) and quits the program (Close)."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/optionsdialog.cpp:128 src/optionsdialog.cpp:136
|
|
||||||
msgid "Language"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/optionsdialog.cpp:131
|
|
||||||
msgid "System language"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/optionsdialog.cpp:141
|
|
||||||
msgid "The interface switches right away, including the tray menu."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/optionsdialog.cpp:110
|
|
||||||
msgid "Start with the session"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/optionsdialog.cpp:122
|
|
||||||
msgid ""
|
|
||||||
"Opens this window when you log in. Not needed for the G keys - the daemon "
|
|
||||||
"does the work and is the only part that has to run."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/optionsdialog.cpp:202
|
|
||||||
msgid "No tray is available on this desktop, so the window cannot be hidden."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/optionsdialog.cpp:144
|
|
||||||
msgid "Daemon status"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/optionsdialog.cpp:157
|
|
||||||
msgid "Tray icon"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/optionsdialog.cpp:158
|
|
||||||
msgid "State"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/optionsdialog.cpp:159
|
|
||||||
msgid "Version"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/optionsdialog.cpp:160
|
|
||||||
msgid "Device"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/optionsdialog.cpp:161
|
|
||||||
msgid "Configuration file"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/optionsdialog.cpp:162
|
|
||||||
msgid "Last key"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/optionsdialog.cpp:205
|
|
||||||
msgid "active"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/optionsdialog.cpp:205
|
|
||||||
msgid "not available"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/optionsdialog.cpp:206
|
|
||||||
msgid "running"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/optionsdialog.cpp:206
|
|
||||||
msgid "not reachable"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/optionsdialog.cpp:208
|
|
||||||
msgid "not found"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/help.cpp:34
|
#: src/help.cpp:34
|
||||||
msgid ""
|
msgid ""
|
||||||
"<b>The nine rows</b> – one per G key. Choose the type, then enter or capture "
|
"<b>The nine rows</b> – one per G key. Choose the type, then enter or capture "
|
||||||
@@ -325,14 +59,14 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/help.cpp:37
|
#: src/help.cpp:37
|
||||||
msgid ""
|
msgid ""
|
||||||
"<b>Key (hold)</b> – presses it and keeps it down while the G key is held, "
|
"<b>Key (hold)</b> – presses it and keeps it down while the G key is held, e."
|
||||||
"e.g. for push-to-talk. Only a single step can be held."
|
"g. for push-to-talk. Only a single step can be held."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/help.cpp:39
|
#: src/help.cpp:39
|
||||||
msgid ""
|
msgid ""
|
||||||
"<b>Command</b> – runs a program or a command line, e.g. /usr/bin/firefox "
|
"<b>Command</b> – runs a program or a command line, e.g. /usr/bin/firefox --"
|
||||||
"--new-window."
|
"new-window."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/help.cpp:40
|
#: src/help.cpp:40
|
||||||
@@ -341,8 +75,8 @@ msgstr ""
|
|||||||
|
|
||||||
#: src/help.cpp:41
|
#: src/help.cpp:41
|
||||||
msgid ""
|
msgid ""
|
||||||
"<b>Capture</b> – records the keys you press next: press them, then "
|
"<b>Capture</b> – records the keys you press next: press them, then <i>Done</"
|
||||||
"<i>Done</i>. Modifiers are kept (shift, altgr, …) and the names are layout "
|
"i>. Modifiers are kept (shift, altgr, …) and the names are layout "
|
||||||
"independent, so ctrl+z means the key that types z on your layout."
|
"independent, so ctrl+z means the key that types z on your layout."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -356,16 +90,16 @@ msgstr ""
|
|||||||
#: src/help.cpp:47
|
#: src/help.cpp:47
|
||||||
msgid ""
|
msgid ""
|
||||||
"<b>Daemon status</b> – the footer shows whether gkeymapper-daemon is "
|
"<b>Daemon status</b> – the footer shows whether gkeymapper-daemon is "
|
||||||
"running, which keyboard it found and the last G key that fired. "
|
"running, which keyboard it found and the last G key that fired. <i>Options</"
|
||||||
"<i>Options</i> shows the same in more detail."
|
"i> shows the same in more detail."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/help.cpp:50
|
#: src/help.cpp:50
|
||||||
msgid ""
|
msgid ""
|
||||||
"<b>Autostart</b> – <i>Start with the session</i> only opens this window when "
|
"<b>Autostart</b> – <i>Start with the session</i> opens this window minimized "
|
||||||
"you log in. The G keys work without it: gkeymapper-daemon (a user service) "
|
"to the tray when you log in. The G keys work without it: gkeymapper-daemon "
|
||||||
"reads them and replays the actions, and that daemon is the only part that has "
|
"(a user service) reads them and replays the actions, and that daemon is the "
|
||||||
"to run."
|
"only part that has to run."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/help.cpp:53
|
#: src/help.cpp:53
|
||||||
@@ -373,6 +107,274 @@ msgid ""
|
|||||||
"<b>Tray and window</b> – with <i>Minimize to tray</i> the window is hidden "
|
"<b>Tray and window</b> – with <i>Minimize to tray</i> the window is hidden "
|
||||||
"instead of closed when you close or minimize it; the tray icon shows it "
|
"instead of closed when you close or minimize it; the tray icon shows it "
|
||||||
"again (<i>Show</i>) and quits the program (<i>Close</i>). Starting "
|
"again (<i>Show</i>) and quits the program (<i>Close</i>). Starting "
|
||||||
"gKeyMapper again closes the old window – rows that were not saved are lost "
|
"gKeyMapper again closes the old window – rows that were not saved yet come "
|
||||||
"with it."
|
"along."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/help.cpp:67 src/help.cpp:86 src/main.cpp:635
|
||||||
|
msgid "Help"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/help.cpp:95 src/optionsdialog.cpp:177
|
||||||
|
msgid "Close window"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:76
|
||||||
|
msgid "Daemon: not reachable"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:78
|
||||||
|
msgid "Daemon: running"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:83
|
||||||
|
msgid "Device: not found"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:86
|
||||||
|
#, c-format
|
||||||
|
msgid "Device: %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:91 src/optionsdialog.cpp:228
|
||||||
|
msgid "Configuration error"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:96
|
||||||
|
#, c-format
|
||||||
|
msgid "Last key: %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:139
|
||||||
|
msgid "Choose file"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:142 src/main.cpp:313 src/main.cpp:325
|
||||||
|
msgid "_Cancel"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:143 src/main.cpp:326
|
||||||
|
msgid "_Open"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:173
|
||||||
|
msgid "Choose file..."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:175
|
||||||
|
msgid "e.g. /usr/bin/firefox or custom command"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:177 src/main.cpp:181 src/main.cpp:726
|
||||||
|
msgid "Capture"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:179
|
||||||
|
msgid "e.g. ctrl+w (held while the G key is held)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:183 src/main.cpp:719
|
||||||
|
msgid "e.g. ctrl+c, ctrl+v"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:198
|
||||||
|
#, c-format
|
||||||
|
msgid "Could not save %s.\n"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:206
|
||||||
|
msgid "Mappings saved to "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:216
|
||||||
|
msgid "Could not export mappings.\n"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:277 src/main.cpp:623 src/trayicon.cpp:370 src/trayicon.cpp:500
|
||||||
|
msgid "gKeyMapper"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:310
|
||||||
|
msgid "Export configuration"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:314
|
||||||
|
msgid "_Save"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:322
|
||||||
|
msgid "Import configuration"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:608
|
||||||
|
msgid "gKeyMapper – Logitech G910"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:627
|
||||||
|
msgid "Export configuration..."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:628
|
||||||
|
msgid "Import configuration..."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:631 src/optionsdialog.cpp:83
|
||||||
|
msgid "Options"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:636
|
||||||
|
msgid "About"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:642
|
||||||
|
msgid "Quit"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:656
|
||||||
|
msgid "G1 to G9 of the Logitech G910 remapped to keys or commands"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:677
|
||||||
|
msgid "G-Key"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:677
|
||||||
|
msgid "Type"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:677
|
||||||
|
msgid "Target (Key/Command)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:677
|
||||||
|
msgid "Action"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:689
|
||||||
|
msgid "Key"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:689
|
||||||
|
msgid "Command"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:689
|
||||||
|
msgid "Disabled"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:690
|
||||||
|
msgid "Key (hold)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:761
|
||||||
|
msgid "Save mappings"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:768
|
||||||
|
msgid "Saved to ~/.config/gKeyMapper/mappings.conf"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/main.cpp:779
|
||||||
|
msgid "Daemon: checking..."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/mappings.cpp:41
|
||||||
|
#, c-format
|
||||||
|
msgid "Could not open %s.\n"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/optionsdialog.cpp:97
|
||||||
|
msgid "Window"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/optionsdialog.cpp:99
|
||||||
|
msgid "Minimize to tray"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/optionsdialog.cpp:112
|
||||||
|
msgid "Start with the session"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/optionsdialog.cpp:124
|
||||||
|
msgid ""
|
||||||
|
"Opens this window minimized to the tray when you log in. Not needed for the "
|
||||||
|
"G keys - the daemon does the work and is the only part that has to run."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/optionsdialog.cpp:131 src/optionsdialog.cpp:149
|
||||||
|
msgid "Language"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/optionsdialog.cpp:137
|
||||||
|
msgid "System language"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/optionsdialog.cpp:154
|
||||||
|
msgid "The interface switches right away, including the tray menu."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/optionsdialog.cpp:157
|
||||||
|
msgid "Daemon status"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/optionsdialog.cpp:170
|
||||||
|
msgid "Tray icon"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/optionsdialog.cpp:171
|
||||||
|
msgid "State"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/optionsdialog.cpp:172
|
||||||
|
msgid "Version"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/optionsdialog.cpp:173
|
||||||
|
msgid "Device"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/optionsdialog.cpp:174
|
||||||
|
msgid "Configuration file"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/optionsdialog.cpp:175
|
||||||
|
msgid "Last key"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/optionsdialog.cpp:216
|
||||||
|
msgid ""
|
||||||
|
"Closing or minimizing the window hides it instead of quitting. The tray icon "
|
||||||
|
"shows it again (Show) and quits the program (Close)."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/optionsdialog.cpp:218
|
||||||
|
msgid "No tray is available on this desktop, so the window cannot be hidden."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/optionsdialog.cpp:221
|
||||||
|
msgid "active"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/optionsdialog.cpp:221
|
||||||
|
msgid "not available"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/optionsdialog.cpp:222
|
||||||
|
msgid "running"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/optionsdialog.cpp:222
|
||||||
|
msgid "not reachable"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/optionsdialog.cpp:224
|
||||||
|
msgid "not found"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/trayicon.cpp:136
|
||||||
|
msgid "Show"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/trayicon.cpp:139
|
||||||
|
msgid "Close"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
535
scripts/i18n.sh
Executable file
535
scripts/i18n.sh
Executable file
@@ -0,0 +1,535 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# gKeyMapper - everything that touches po/*.po, po/gkeymapper.pot and the compiled
|
||||||
|
# catalogs, in one place, so a translation change is a command instead of a list of
|
||||||
|
# steps to remember. The rules behind it are written down in po/README.md.
|
||||||
|
#
|
||||||
|
# Usage: scripts/i18n.sh <command> [arguments]
|
||||||
|
# extract regenerate po/gkeymapper.pot from the sources
|
||||||
|
# update [lang...] merge the pot into the catalogs: new strings come in
|
||||||
|
# untranslated, existing translations are kept
|
||||||
|
# check validate the pot, the catalogs and the language list
|
||||||
|
# status per-language overview (counts + compiled catalog)
|
||||||
|
# todo <lang> what is still untranslated or fuzzy, with the source
|
||||||
|
# locations from the pot
|
||||||
|
# new <lang> [name] create po/<lang>.po from the pot (name = Language-Team,
|
||||||
|
# e.g. "Italiano"); "new en" writes the identity catalog
|
||||||
|
# help this text
|
||||||
|
#
|
||||||
|
# Only gettext tools and coreutils are used. The tools can be overridden from the
|
||||||
|
# environment, like the Makefile's variables: XGETTEXT=... scripts/i18n.sh extract
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
# Everything is addressed relative to the repository root and run from there: the
|
||||||
|
# source locations xgettext writes into the pot ("src/main.cpp:582") and the file
|
||||||
|
# names the gettext tools print are relative to the current directory, and absolute
|
||||||
|
# paths would end up in the pot and in every message this script shows.
|
||||||
|
cd -- "$ROOT" || exit 1
|
||||||
|
PO_DIR="po"
|
||||||
|
POT="$PO_DIR/gkeymapper.pot"
|
||||||
|
LOCALE_DIR="locale"
|
||||||
|
PACKAGE="gkeymapper"
|
||||||
|
# What the catalogs carry in Project-Id-Version (see po/*.po).
|
||||||
|
PO_VERSION="0.1"
|
||||||
|
|
||||||
|
XGETTEXT=${XGETTEXT:-xgettext}
|
||||||
|
MSGFMT=${MSGFMT:-msgfmt}
|
||||||
|
MSGMERGE=${MSGMERGE:-msgmerge}
|
||||||
|
MSGATTRIB=${MSGATTRIB:-msgattrib}
|
||||||
|
MSGCAT=${MSGCAT:-msgcat}
|
||||||
|
|
||||||
|
# The counts and message lists below are parsed, so the tools have to answer in
|
||||||
|
# English, whatever the session's language is.
|
||||||
|
export LC_ALL=C
|
||||||
|
|
||||||
|
# A temp file for a pot, removed on every exit - also on the error paths, where it
|
||||||
|
# is the half-written file that must not stay behind.
|
||||||
|
POT_TMP=""
|
||||||
|
trap 'rm -f "${POT_TMP:-}"' EXIT
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# helpers
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
die() {
|
||||||
|
printf 'i18n: %s\n' "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
note() {
|
||||||
|
printf '%s\n' "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
require_tools() {
|
||||||
|
local tool
|
||||||
|
for tool in "$@"; do
|
||||||
|
command -v "$tool" >/dev/null 2>&1 ||
|
||||||
|
die "$tool not found - install gettext (see README, Requirements)"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Every source that may hold translatable strings: the GUI, which is the only
|
||||||
|
# program with gettext set up (src/main.cpp) - the same two directories the Makefile
|
||||||
|
# compiles into it. The daemon is English on purpose (see `check`), so its sources
|
||||||
|
# are not scanned. The pot is generated from this list, in this order, so `extract`
|
||||||
|
# is reproducible: a hand-ordered file list once made the pot's message order depend
|
||||||
|
# on the machine it ran on.
|
||||||
|
sources() {
|
||||||
|
local file
|
||||||
|
for file in "$ROOT"/src/*.cpp "$ROOT"/shared/*.cpp; do
|
||||||
|
if [ -e "$file" ]; then printf '%s\n' "${file#"$ROOT"/}"; fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# The languages that have a catalog, i.e. po/*.po - the same list the Makefile
|
||||||
|
# compiles and installs.
|
||||||
|
languages() {
|
||||||
|
local file
|
||||||
|
for file in "$PO_DIR"/*.po; do
|
||||||
|
if [ -e "$file" ]; then basename "$file" .po; fi
|
||||||
|
done | sort
|
||||||
|
}
|
||||||
|
|
||||||
|
# The messages of a po/pot, one per line and multi-line strings joined: msgcat does
|
||||||
|
# the joining, so `_("a" "b")` in the sources and the pot entry are the same string.
|
||||||
|
# The header (empty msgid) is left out.
|
||||||
|
msgids() {
|
||||||
|
"$MSGCAT" --stringtable-output "$1" 2>/dev/null |
|
||||||
|
sed -n 's/^"\(.*\)" = .*$/\1/p' | sed '/^$/d'
|
||||||
|
}
|
||||||
|
|
||||||
|
# translated/fuzzy/untranslated of one catalog, into three globals. msgfmt prints
|
||||||
|
# them as a comma separated list ("81 translated messages, 1 untranslated
|
||||||
|
# message.") and leaves the parts out that are zero, so the list is split first -
|
||||||
|
# a pattern like ".*\([0-9]\+\) untranslated" would stop at the last digit of a
|
||||||
|
# count and read 82 as 2.
|
||||||
|
stats() {
|
||||||
|
local out parts
|
||||||
|
out=$("$MSGFMT" --statistics -o /dev/null "$1" 2>&1) || die "$MSGFMT rejected $1"
|
||||||
|
parts=$(printf '%s\n' "$out" | tr ',' '\n')
|
||||||
|
translated=$(printf '%s\n' "$parts" | sed -n 's/^ *\([0-9]\+\) translated.*/\1/p'); : "${translated:=0}"
|
||||||
|
fuzzy=$(printf '%s\n' "$parts" | sed -n 's/^ *\([0-9]\+\) fuzzy.*/\1/p'); : "${fuzzy:=0}"
|
||||||
|
untranslated=$(printf '%s\n' "$parts" | sed -n 's/^ *\([0-9]\+\) untranslated.*/\1/p'); : "${untranslated:=0}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# The `Language:` header of a catalog, so a copy-pasted file is noticed.
|
||||||
|
po_language() {
|
||||||
|
sed -n 's/^"Language: \(.*\)\\n"$/\1/p' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# The POT-Creation-Date of a po/pot. It does not decide whether a file is up to
|
||||||
|
# date (the message set does); it tells whether a merge happened after the last
|
||||||
|
# extract.
|
||||||
|
pot_date() {
|
||||||
|
sed -n 's/^"POT-Creation-Date: \(.*\)\\n"$/\1/p' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# One line per entry whose msgstr is not the msgid again - the rule for po/en.po,
|
||||||
|
# which is an identity catalog because English is the source language. Compared as
|
||||||
|
# raw (still escaped) text: both sides come from the same file, so the escaping
|
||||||
|
# matches. Plurals need more than this and are not used anywhere (no ngettext).
|
||||||
|
identity_mismatches() {
|
||||||
|
awk '
|
||||||
|
function quoted(line) { sub(/^[^"]*"/, "", line); sub(/"$/, "", line); return line }
|
||||||
|
function check() { if (id != "" && str != id) { print id; mismatches++ } }
|
||||||
|
/^msgid_plural|^msgstr\[/ { print "plural entries are not supported"; exit 2 }
|
||||||
|
/^msgid / { check(); id = quoted($0); str = ""; in_id = 1; next }
|
||||||
|
/^msgstr / { str = quoted($0); in_id = 0; next }
|
||||||
|
/^"/ { if (in_id) id = id quoted($0); else str = str quoted($0); next }
|
||||||
|
/^#/ { next }
|
||||||
|
/^[[:space:]]*$/ { next }
|
||||||
|
END { check(); exit (mismatches > 0 ? 1 : 0) }
|
||||||
|
' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# The pot as xgettext writes it right now, into the given file. `_()` marks
|
||||||
|
# strings that are translated when they are used, `N_()` the ones that are only
|
||||||
|
# collected (the help text is assembled later on); both are named explicitly,
|
||||||
|
# because a gettext that does not know `_` by itself is not unheard of.
|
||||||
|
extract_to() {
|
||||||
|
local out="$1" file
|
||||||
|
require_tools "$XGETTEXT"
|
||||||
|
local -a files=()
|
||||||
|
while IFS= read -r file; do files+=("$file"); done < <(sources)
|
||||||
|
[ "${#files[@]}" -gt 0 ] || die "no sources found under src/ or shared/"
|
||||||
|
|
||||||
|
"$XGETTEXT" --from-code=UTF-8 --language=C++ --keyword=_ --keyword=N_ \
|
||||||
|
--output="$out" "${files[@]}" || die "$XGETTEXT failed"
|
||||||
|
|
||||||
|
# Nothing or header-only means the extraction is broken, never that the program
|
||||||
|
# has no strings. Checked before the pot is replaced, so a broken gettext
|
||||||
|
# cannot empty a good pot.
|
||||||
|
[ -s "$out" ] || die "$XGETTEXT produced no output (is gettext working? $XGETTEXT --version)"
|
||||||
|
[ "$(grep -c '^msgid "' "$out")" -gt 1 ] ||
|
||||||
|
die "$XGETTEXT extracted no message - are the keywords right?"
|
||||||
|
}
|
||||||
|
|
||||||
|
# The pot without its header entry and without source locations: the entries of a
|
||||||
|
# fresh catalog. With identity=1 every msgstr repeats its msgid (po/en.po).
|
||||||
|
pot_body() {
|
||||||
|
awk -v identity="$2" '
|
||||||
|
NR == 1 { skipping = 1 }
|
||||||
|
skipping { if ($0 ~ /^$/) skipping = 0; next }
|
||||||
|
/^#:/ { next }
|
||||||
|
/^msgid / {
|
||||||
|
if (identity) { buffer = $0; sub(/^msgid/, "msgstr", buffer); collecting = 1 }
|
||||||
|
print; next
|
||||||
|
}
|
||||||
|
/^"/ { if (collecting) buffer = buffer "\n" $0; print; next }
|
||||||
|
/^msgstr ""$/ {
|
||||||
|
if (collecting) { print buffer; collecting = 0; next }
|
||||||
|
print; next
|
||||||
|
}
|
||||||
|
{ print }
|
||||||
|
' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# The language codes the GUI offers (src/optionsdialog.cpp, without the leading
|
||||||
|
# "" for "follow the session"). A catalog that is not in this list cannot be
|
||||||
|
# picked; a code without a catalog is a window that stays English.
|
||||||
|
gui_language_codes() {
|
||||||
|
sed -n 's/.*kLanguageCodes\[\] *= *{\(.*\)};.*/\1/p' "src/optionsdialog.cpp" |
|
||||||
|
tr ',' '\n' | tr -d ' "' | sed '/^$/d'
|
||||||
|
}
|
||||||
|
|
||||||
|
# The labels of that dropdown: one more than the codes, the first being
|
||||||
|
# "System language".
|
||||||
|
gui_language_labels() {
|
||||||
|
sed -n '/languageOptions\[\] *= *{/,/nullptr/p' "src/optionsdialog.cpp" |
|
||||||
|
sed 's/.*{//; s/nullptr.*//' | tr ',' '\n' | grep -c '[^[:space:]]'
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# commands
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
cmd_extract() {
|
||||||
|
POT_TMP=$(mktemp "${TMPDIR:-/tmp}/$PACKAGE-pot.XXXXXX")
|
||||||
|
extract_to "$POT_TMP"
|
||||||
|
|
||||||
|
if [ -f "$POT" ]; then
|
||||||
|
local added gone
|
||||||
|
added=$(comm -13 <(msgids "$POT" | sort) <(msgids "$POT_TMP" | sort))
|
||||||
|
gone=$(comm -23 <(msgids "$POT" | sort) <(msgids "$POT_TMP" | sort))
|
||||||
|
note "po/gkeymapper.pot: $(msgids "$POT_TMP" | wc -l) messages"
|
||||||
|
[ -z "$added" ] || { note "new:"; printf '%s\n' "$added" | sed 's/^/ /'; }
|
||||||
|
[ -z "$gone" ] || { note "gone:"; printf '%s\n' "$gone" | sed 's/^/ /'; }
|
||||||
|
else
|
||||||
|
note "po/gkeymapper.pot: $(msgids "$POT_TMP" | wc -l) messages"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mv "$POT_TMP" "$POT"
|
||||||
|
POT_TMP="" # moved, nothing to clean up
|
||||||
|
note "wrote po/gkeymapper.pot - now: scripts/i18n.sh update"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_update() {
|
||||||
|
local lang po
|
||||||
|
require_tools "$MSGMERGE" "$MSGFMT"
|
||||||
|
[ -f "$POT" ] || die "$POT is missing - run: scripts/i18n.sh extract"
|
||||||
|
|
||||||
|
local -a targets
|
||||||
|
if [ "$#" -gt 0 ]; then targets=("$@"); else mapfile -t targets < <(languages); fi
|
||||||
|
[ "${#targets[@]}" -gt 0 ] || die "no po/*.po to update"
|
||||||
|
|
||||||
|
for lang in "${targets[@]}"; do
|
||||||
|
po="$PO_DIR/$lang.po"
|
||||||
|
[ -f "$po" ] || die "$po does not exist (a new language: scripts/i18n.sh new $lang)"
|
||||||
|
# --no-location: the catalogs carry no "#: file:line" comments on purpose -
|
||||||
|
# they are noise for translators and churn whenever a line moves. The
|
||||||
|
# locations live in the pot, and `todo` shows them from there.
|
||||||
|
"$MSGMERGE" --quiet --update --no-location "$po" "$POT"
|
||||||
|
# msgmerge keeps the previous file as <po>~; a backup in git is noise.
|
||||||
|
rm -f "$po~"
|
||||||
|
stats "$po"
|
||||||
|
note "po/$lang.po: $translated translated, $fuzzy fuzzy, $untranslated untranslated"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_check() {
|
||||||
|
local lang po code out failures=0 stale_dates=""
|
||||||
|
require_tools "$MSGFMT" "$MSGATTRIB" "$MSGCAT" "$MSGMERGE" "$XGETTEXT"
|
||||||
|
[ -f "$POT" ] || die "$POT is missing"
|
||||||
|
|
||||||
|
local langs
|
||||||
|
langs=$(languages)
|
||||||
|
[ -n "$langs" ] || die "no po/*.po found"
|
||||||
|
|
||||||
|
# 1. Is the pot what the sources say? A message that was added or changed
|
||||||
|
# without touching the pot is what this catches - the source locations only
|
||||||
|
# drift when a line moves, which is worth a note, not a failure.
|
||||||
|
POT_TMP=$(mktemp "${TMPDIR:-/tmp}/$PACKAGE-pot.XXXXXX")
|
||||||
|
extract_to "$POT_TMP"
|
||||||
|
local added gone
|
||||||
|
added=$(comm -13 <(msgids "$POT" | sort) <(msgids "$POT_TMP" | sort))
|
||||||
|
gone=$(comm -23 <(msgids "$POT" | sort) <(msgids "$POT_TMP" | sort))
|
||||||
|
if [ -n "$added" ] || [ -n "$gone" ]; then
|
||||||
|
note "po/gkeymapper.pot is out of date:"
|
||||||
|
[ -z "$added" ] || { note " in the sources, not in the pot:"; printf '%s\n' "$added" | sed 's/^/ /'; }
|
||||||
|
[ -z "$gone" ] || { note " in the pot, not in the sources:"; printf '%s\n' "$gone" | sed 's/^/ /'; }
|
||||||
|
note " fix: scripts/i18n.sh extract && scripts/i18n.sh update"
|
||||||
|
failures=$((failures + 1))
|
||||||
|
elif ! diff -q <(grep -v '^"POT-Creation-Date:' "$POT_TMP") \
|
||||||
|
<(grep -v '^"POT-Creation-Date:' "$POT") >/dev/null; then
|
||||||
|
note "po/gkeymapper.pot has the same messages, but an older extract (line numbers, order)"
|
||||||
|
note " only the locations in 'todo' are affected - scripts/i18n.sh extract refreshes them"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. Every catalog: parsable, complete, translated, and named what it says.
|
||||||
|
for lang in $langs; do
|
||||||
|
po="$PO_DIR/$lang.po"
|
||||||
|
|
||||||
|
# -c checks the format strings (%s, %d, ...) and the entry structure.
|
||||||
|
if ! out=$("$MSGFMT" -c -o /dev/null "$po" 2>&1); then
|
||||||
|
note "$lang: $MSGFMT -c failed:"
|
||||||
|
printf '%s\n' "$out" | sed 's/^/ /'
|
||||||
|
failures=$((failures + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Does the catalog define the same messages as the pot? Compared as two
|
||||||
|
# lists rather than with msgcmp, which would also report every untranslated
|
||||||
|
# entry and drown the real problem (the counts below say what is missing).
|
||||||
|
local missing extra
|
||||||
|
missing=$(comm -23 <(msgids "$POT" | sort) <(msgids "$po" | sort))
|
||||||
|
extra=$(comm -13 <(msgids "$POT" | sort) <(msgids "$po" | sort))
|
||||||
|
if [ -n "$missing" ]; then
|
||||||
|
note "$lang: $po does not define:"
|
||||||
|
printf '%s\n' "$missing" | sed 's/^/ /'
|
||||||
|
failures=$((failures + 1))
|
||||||
|
fi
|
||||||
|
if [ -n "$extra" ]; then
|
||||||
|
note "$lang: $po defines messages the pot does not know:"
|
||||||
|
printf '%s\n' "$extra" | sed 's/^/ /'
|
||||||
|
failures=$((failures + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
stats "$po"
|
||||||
|
if [ "$untranslated" -ne 0 ]; then
|
||||||
|
note "$lang: $untranslated untranslated message(s) - scripts/i18n.sh todo $lang"
|
||||||
|
failures=$((failures + 1))
|
||||||
|
fi
|
||||||
|
if [ "$fuzzy" -ne 0 ]; then
|
||||||
|
note "$lang: $fuzzy fuzzy message(s) - those are shown in English"
|
||||||
|
failures=$((failures + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Strings that vanished from the program: msgmerge keeps them as "#~"
|
||||||
|
# entries. Not an error, but stale food for the next translator.
|
||||||
|
local obsolete=0
|
||||||
|
obsolete=$(grep -c '^#~' "$po" || true)
|
||||||
|
[ "$obsolete" -eq 0 ] ||
|
||||||
|
note "$lang: $obsolete obsolete entries (gone from the sources; msgattrib --no-obsolete drops them)"
|
||||||
|
|
||||||
|
if [ "$(po_language "$po")" != "$lang" ]; then
|
||||||
|
note "$lang: the header says Language: $(po_language "$po")"
|
||||||
|
failures=$((failures + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# English has a catalog of its own (an explicit language has to work on a
|
||||||
|
# German desktop) and it is an identity one.
|
||||||
|
if [ "$lang" = "en" ]; then
|
||||||
|
if ! out=$(identity_mismatches "$po"); then
|
||||||
|
note "en: po/en.po is not an identity catalog:"
|
||||||
|
printf '%s\n' "$out" | head -20 | sed 's/^/ /'
|
||||||
|
failures=$((failures + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# A note, not a failure: it only means the file was not merged after the
|
||||||
|
# last extract, so its header carries an older date than the pot. Collected
|
||||||
|
# into one line - it says nothing about a single language.
|
||||||
|
if [ "$(pot_date "$po")" != "$(pot_date "$POT")" ]; then
|
||||||
|
stale_dates="$stale_dates $lang"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
[ -z "$stale_dates" ] ||
|
||||||
|
note "not merged against the current pot:$stale_dates (scripts/i18n.sh update)"
|
||||||
|
|
||||||
|
# 3. The languages the GUI offers (src/optionsdialog.cpp): a catalog that cannot
|
||||||
|
# be selected is dead weight, a code without a catalog is an English window
|
||||||
|
# with a German label.
|
||||||
|
local codes
|
||||||
|
codes=$(gui_language_codes)
|
||||||
|
[ -n "$codes" ] || die "could not read kLanguageCodes[] from src/optionsdialog.cpp"
|
||||||
|
for lang in $langs; do
|
||||||
|
if ! grep -qx "$lang" <<<"$codes"; then
|
||||||
|
note "po/$lang.po exists, but $lang is not offered in the options window"
|
||||||
|
note " add it to kLanguageCodes[] and languageOptions[] (src/optionsdialog.cpp)"
|
||||||
|
failures=$((failures + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
for code in $codes; do
|
||||||
|
if [ ! -f "$PO_DIR/$code.po" ]; then
|
||||||
|
note "the options window offers $code, but po/$code.po is missing"
|
||||||
|
failures=$((failures + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ "$(gui_language_labels)" -ne "$(( $(printf '%s\n' "$codes" | wc -l) + 1 ))" ]; then
|
||||||
|
note "kLanguageCodes[] and languageOptions[] differ in length (src/optionsdialog.cpp)"
|
||||||
|
failures=$((failures + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. Strings marked in the daemon. It has no gettext setup (no setlocale, no
|
||||||
|
# bindtextdomain - see daemon/main.cpp), so a `_()` there would end up in the
|
||||||
|
# pot, get translated, and still show English forever. The GUI is the
|
||||||
|
# translated program.
|
||||||
|
local daemon_marked
|
||||||
|
daemon_marked=$(grep -lE '(^|[^[:alnum:]_])_\("|N_\("' daemon/*.cpp 2>/dev/null || true)
|
||||||
|
if [ -n "$daemon_marked" ]; then
|
||||||
|
note "the daemon is not translated, but these files mark strings:"
|
||||||
|
printf '%s\n' "$daemon_marked" | sed 's/^/ /'
|
||||||
|
note " either drop the marker, or give the daemon its own gettext setup and"
|
||||||
|
note " add daemon/ to sources() in scripts/i18n.sh"
|
||||||
|
failures=$((failures + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$failures" -gt 0 ]; then
|
||||||
|
note "i18n: $failures problem(s)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
note "i18n: $(printf '%s\n' "$langs" | wc -l) languages, $(msgids "$POT" | wc -l) messages, everything in sync"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_status() {
|
||||||
|
local lang po mo mo_state
|
||||||
|
require_tools "$MSGFMT" "$MSGCAT"
|
||||||
|
[ -f "$POT" ] || die "$POT is missing"
|
||||||
|
printf '%-4s %-8s %10s %6s %11s %-9s %s\n' \
|
||||||
|
lang messages translated fuzzy untranslated catalog file
|
||||||
|
for lang in $(languages); do
|
||||||
|
po="$PO_DIR/$lang.po"
|
||||||
|
stats "$po"
|
||||||
|
mo="$LOCALE_DIR/$lang/LC_MESSAGES/$PACKAGE.mo"
|
||||||
|
mo_state="not built"
|
||||||
|
if [ -f "$mo" ]; then
|
||||||
|
# `make translations` rebuilds a catalog that is older than its po.
|
||||||
|
if [ "$mo" -nt "$po" ]; then mo_state="current"; else mo_state="stale"; fi
|
||||||
|
fi
|
||||||
|
printf '%-4s %-8s %10s %6s %11s %-9s %s\n' "$lang" \
|
||||||
|
"$((translated + fuzzy + untranslated))" "$translated" "$fuzzy" "$untranslated" \
|
||||||
|
"$mo_state" "po/$lang.po"
|
||||||
|
done
|
||||||
|
printf '\npot: %s messages, %s\n' "$(msgids "$POT" | wc -l)" "$(pot_date "$POT")"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_todo() {
|
||||||
|
local lang="${1:-}"
|
||||||
|
[ -n "$lang" ] || die "usage: scripts/i18n.sh todo <lang>"
|
||||||
|
local po="$PO_DIR/$lang.po"
|
||||||
|
[ -f "$po" ] || die "$po does not exist"
|
||||||
|
require_tools "$MSGMERGE" "$MSGATTRIB"
|
||||||
|
|
||||||
|
# Merging the pot into the catalog is what puts the source locations next to
|
||||||
|
# the messages - the catalogs themselves have none (see cmd_update).
|
||||||
|
local open
|
||||||
|
open=$("$MSGMERGE" --quiet "$po" "$POT" | "$MSGATTRIB" --untranslated --no-obsolete)
|
||||||
|
if [ -n "$open" ]; then
|
||||||
|
# A valid po comment, so the output can be kept as a file and translated.
|
||||||
|
note "# --- untranslated in po/$lang.po ---"
|
||||||
|
printf '%s\n' "$open"
|
||||||
|
else
|
||||||
|
note "po/$lang.po: nothing untranslated"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local fuzzy
|
||||||
|
fuzzy=$("$MSGMERGE" --quiet "$po" "$POT" | "$MSGATTRIB" --only-fuzzy --no-obsolete)
|
||||||
|
if [ -n "$fuzzy" ]; then
|
||||||
|
note
|
||||||
|
note "# --- fuzzy: shown in English until reviewed, the old text is kept as a comment ---"
|
||||||
|
printf '%s\n' "$fuzzy"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_new() {
|
||||||
|
local lang="${1:-}" team="${2:-}"
|
||||||
|
[ -n "$lang" ] || die "usage: scripts/i18n.sh new <lang> [Language-Team]"
|
||||||
|
case "$lang" in
|
||||||
|
*[!a-zA-Z_@.-]*) die "unexpected language code: $lang" ;;
|
||||||
|
esac
|
||||||
|
[ -f "$POT" ] || die "$POT is missing - run: scripts/i18n.sh extract"
|
||||||
|
local po="$PO_DIR/$lang.po"
|
||||||
|
[ -e "$po" ] && die "$po already exists"
|
||||||
|
if [ -z "$team" ]; then
|
||||||
|
# The two catalogs for a language that has been here from the start carry
|
||||||
|
# the English name of the language; anything else falls back to its code.
|
||||||
|
case "$lang" in
|
||||||
|
en) team="English" ;;
|
||||||
|
*) team="$lang" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
if [ "$lang" = "en" ]; then
|
||||||
|
printf '# English translation for %s.\n' "$PACKAGE"
|
||||||
|
printf '# English is the source language; this catalog keeps the strings explicit.\n'
|
||||||
|
else
|
||||||
|
printf '# %s translation for %s.\n' "$team" "$PACKAGE"
|
||||||
|
printf '# Copyright (C) %s\n' "$(date '+%Y')"
|
||||||
|
printf '# This file is distributed under the same license as the %s package.\n' "$PACKAGE"
|
||||||
|
fi
|
||||||
|
printf '#\n'
|
||||||
|
# The header in the shape the other catalogs use, so a new language does not
|
||||||
|
# start out looking different.
|
||||||
|
cat <<EOF
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: $PACKAGE $PO_VERSION\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: $(pot_date "$POT")\n"
|
||||||
|
"PO-Revision-Date: $(date '+%Y-%m-%d %H:%M%z')\n"
|
||||||
|
"Last-Translator: \n"
|
||||||
|
"Language-Team: $team\n"
|
||||||
|
"Language: $lang\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
EOF
|
||||||
|
printf '\n'
|
||||||
|
# The entries come from the pot: it defines what has to be translated, and
|
||||||
|
# every msgstr in it is already empty.
|
||||||
|
pot_body "$POT" "$([ "$lang" = "en" ] && echo 1 || echo 0)"
|
||||||
|
} >"$po"
|
||||||
|
|
||||||
|
note "wrote po/$lang.po"
|
||||||
|
if [ "$lang" != "en" ]; then
|
||||||
|
note "next:"
|
||||||
|
note " translate it, then: scripts/i18n.sh check && make translations"
|
||||||
|
note " offer it in the options window, or nobody can pick it:"
|
||||||
|
note " src/optionsdialog.cpp: kLanguageCodes[] and languageOptions[]"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
scripts/i18n.sh <command> [arguments]
|
||||||
|
|
||||||
|
extract regenerate po/gkeymapper.pot from the sources
|
||||||
|
update [lang...] merge the pot into the catalogs: new strings come in
|
||||||
|
untranslated, existing translations are kept
|
||||||
|
check validate the pot, the catalogs and the language list
|
||||||
|
status per-language overview (counts + compiled catalog)
|
||||||
|
todo <lang> what is still untranslated or fuzzy, with the source
|
||||||
|
locations from the pot
|
||||||
|
new <lang> [name] create po/<lang>.po from the pot (name = Language-Team,
|
||||||
|
e.g. "Italiano"); "new en" writes the identity catalog
|
||||||
|
help this text
|
||||||
|
|
||||||
|
The rules behind the commands are in po/README.md. gettext tools are used from
|
||||||
|
PATH; override them like the Makefile's variables, e.g. XGETTEXT=/usr/bin/xgettext
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
command="${1:-help}"
|
||||||
|
shift || true
|
||||||
|
case "$command" in
|
||||||
|
extract|pot) cmd_extract "$@" ;;
|
||||||
|
update) cmd_update "$@" ;;
|
||||||
|
check) cmd_check "$@" ;;
|
||||||
|
status) cmd_status "$@" ;;
|
||||||
|
todo) cmd_todo "$@" ;;
|
||||||
|
new) cmd_new "$@" ;;
|
||||||
|
help|-h|--help) usage ;;
|
||||||
|
*) usage; exit 1 ;;
|
||||||
|
esac
|
||||||
@@ -24,6 +24,16 @@ inline constexpr const char* kBusName = "dev.rager.gkeymapper.Daemon";
|
|||||||
inline constexpr const char* kObjectPath = "/dev/rager/gkeymapper/Daemon";
|
inline constexpr const char* kObjectPath = "/dev/rager/gkeymapper/Daemon";
|
||||||
inline constexpr const char* kInterface = "dev.rager.gkeymapper.Daemon";
|
inline constexpr const char* kInterface = "dev.rager.gkeymapper.Daemon";
|
||||||
|
|
||||||
|
// Where the GUI answers a second start (see src/singleinstance and src/main.cpp):
|
||||||
|
// it hands out the rows its window shows, so an instance that is replaced does not
|
||||||
|
// take unsaved edits with it. The name is the app id itself - GtkApplication owns
|
||||||
|
// that (see kBusName above), and the GUI is not a service, it only answers while a
|
||||||
|
// window is up.
|
||||||
|
inline constexpr const char* kWindowObjectPath = "/dev/rager/gkeymapper/Window";
|
||||||
|
inline constexpr const char* kWindowInterface = "dev.rager.gkeymapper.Window";
|
||||||
|
// The one method: no arguments, the rows as the mappings text (the file format).
|
||||||
|
inline constexpr const char* kWindowRowsMethod = "GetRows";
|
||||||
|
|
||||||
} // namespace gkeymapper::ipc
|
} // namespace gkeymapper::ipc
|
||||||
|
|
||||||
#endif // GKEYMAPPER_IPC_IDS_H
|
#endif // GKEYMAPPER_IPC_IDS_H
|
||||||
|
|||||||
@@ -95,8 +95,10 @@ bool set_autostart_enabled(bool enabled) {
|
|||||||
// About dialog, so no translator has to carry a second sentence for it.
|
// About dialog, so no translator has to carry a second sentence for it.
|
||||||
g_key_file_set_string(file, kGroup, "Comment",
|
g_key_file_set_string(file, kGroup, "Comment",
|
||||||
_("G-Key mapper for the Logitech G910 (GTK4)."));
|
_("G-Key mapper for the Logitech G910 (GTK4)."));
|
||||||
// Quoted: a path may contain a space.
|
// Quoted: a path may contain a space. The flag keeps the window out of the way
|
||||||
g_autofree gchar* command = g_strdup_printf("\"%s\"", program_path().c_str());
|
// at login, see kStartHiddenArg.
|
||||||
|
g_autofree gchar* command =
|
||||||
|
g_strdup_printf("\"%s\" %s", program_path().c_str(), kStartHiddenArg);
|
||||||
g_key_file_set_string(file, kGroup, "Exec", command);
|
g_key_file_set_string(file, kGroup, "Exec", command);
|
||||||
g_key_file_set_string(file, kGroup, "Icon", "gkeymapper");
|
g_key_file_set_string(file, kGroup, "Icon", "gkeymapper");
|
||||||
g_key_file_set_boolean(file, kGroup, "Terminal", FALSE);
|
g_key_file_set_boolean(file, kGroup, "Terminal", FALSE);
|
||||||
|
|||||||
@@ -11,6 +11,13 @@ namespace gkeymapper {
|
|||||||
// else - no flag in gui.conf that could disagree with it.
|
// else - no flag in gui.conf that could disagree with it.
|
||||||
std::string autostart_file_path();
|
std::string autostart_file_path();
|
||||||
|
|
||||||
|
// The argument the entry hands to the program: the window is then built but stays
|
||||||
|
// in the tray (main.cpp), so a login does not put a window in front of the user,
|
||||||
|
// and asking for it - the tray's Show, or starting gKeyMapper by hand - brings it
|
||||||
|
// up. It belongs on the Exec line and not in gui.conf: the entry is what the
|
||||||
|
// session runs, so the flag is the whole truth about what happens at login.
|
||||||
|
inline constexpr const char* kStartHiddenArg = "--start-hidden";
|
||||||
|
|
||||||
// True when that file exists and starts the program. An entry the desktop's own
|
// True when that file exists and starts the program. An entry the desktop's own
|
||||||
// tool has switched off (Hidden=true, or X-GNOME-Autostart-enabled=false) counts
|
// tool has switched off (Hidden=true, or X-GNOME-Autostart-enabled=false) counts
|
||||||
// as off as well, so the switch shows what really happens at login.
|
// as off as well, so the switch shows what really happens at login.
|
||||||
|
|||||||
@@ -47,13 +47,13 @@ const char* kParagraphs[] = {
|
|||||||
N_("<b>Daemon status</b> – the footer shows whether gkeymapper-daemon is running, which "
|
N_("<b>Daemon status</b> – the footer shows whether gkeymapper-daemon is running, which "
|
||||||
"keyboard it found and the last G key that fired. <i>Options</i> shows the same in "
|
"keyboard it found and the last G key that fired. <i>Options</i> shows the same in "
|
||||||
"more detail."),
|
"more detail."),
|
||||||
N_("<b>Autostart</b> – <i>Start with the session</i> only opens this window when you log "
|
N_("<b>Autostart</b> – <i>Start with the session</i> opens this window minimized to the tray "
|
||||||
"in. The G keys work without it: gkeymapper-daemon (a user service) reads them and "
|
"when you log in. The G keys work without it: gkeymapper-daemon (a user service) reads "
|
||||||
"replays the actions, and that daemon is the only part that has to run."),
|
"them and replays the actions, and that daemon is the only part that has to run."),
|
||||||
N_("<b>Tray and window</b> – with <i>Minimize to tray</i> the window is hidden instead of "
|
N_("<b>Tray and window</b> – with <i>Minimize to tray</i> the window is hidden instead of "
|
||||||
"closed when you close or minimize it; the tray icon shows it again (<i>Show</i>) and "
|
"closed when you close or minimize it; the tray icon shows it again (<i>Show</i>) and "
|
||||||
"quits the program (<i>Close</i>). Starting gKeyMapper again closes the old window – "
|
"quits the program (<i>Close</i>). Starting gKeyMapper again closes the old window – "
|
||||||
"rows that were not saved are lost with it."),
|
"rows that were not saved yet come along."),
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|||||||
109
src/main.cpp
109
src/main.cpp
@@ -18,6 +18,7 @@
|
|||||||
#include "mapping.h"
|
#include "mapping.h"
|
||||||
#include "mappings.h"
|
#include "mappings.h"
|
||||||
#include "optionsdialog.h"
|
#include "optionsdialog.h"
|
||||||
|
#include "popovers.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "singleinstance.h"
|
#include "singleinstance.h"
|
||||||
#include "trayicon.h"
|
#include "trayicon.h"
|
||||||
@@ -37,6 +38,18 @@ using gkeymapper::kNumKeys;
|
|||||||
|
|
||||||
std::vector<KeyRow> rows;
|
std::vector<KeyRow> rows;
|
||||||
|
|
||||||
|
// The rows of the instance this start replaced, as a mappings text (empty when
|
||||||
|
// there was none, or when it did not hand them over - an older build). main()
|
||||||
|
// fills this in before the window exists; build_ui() starts from it instead of
|
||||||
|
// from the file, so edits that were never saved survive a second start.
|
||||||
|
std::string handedOverRows;
|
||||||
|
|
||||||
|
// Set by main() when the session's autostart entry started this instance (the
|
||||||
|
// --start-hidden argument): the window is built but stays in the tray, so a login
|
||||||
|
// does not put it in the way. on_activate is what reads it - a window that is
|
||||||
|
// built later (a language switch) comes up the way it was left.
|
||||||
|
bool startHidden = false;
|
||||||
|
|
||||||
// The daemon view behind the footer. It is filled by the asynchronous callbacks
|
// The daemon view behind the footer. It is filled by the asynchronous callbacks
|
||||||
// of DaemonClient, which arrive on the GTK main loop, so nothing here ever waits
|
// of DaemonClient, which arrive on the GTK main loop, so nothing here ever waits
|
||||||
// for the daemon; the options window shows the same data in full.
|
// for the daemon; the options window shows the same data in full.
|
||||||
@@ -373,6 +386,10 @@ static std::string find_asset(const std::string& name) {
|
|||||||
// Size of the pixmap handed to the panel; the panel scales it to its own size.
|
// Size of the pixmap handed to the panel; the panel scales it to its own size.
|
||||||
constexpr int kTrayIconSize = 64;
|
constexpr int kTrayIconSize = 64;
|
||||||
|
|
||||||
|
// How long a start from the autostart entry waits for the tray icon before it
|
||||||
|
// gives up and shows the window (on_start_hidden_timeout).
|
||||||
|
constexpr guint kStartHiddenGraceMs = 5000;
|
||||||
|
|
||||||
// Shown and hidden are the two states the tray works with: a hidden window is
|
// Shown and hidden are the two states the tray works with: a hidden window is
|
||||||
// still a window, so the app keeps running and the tray stays the way back.
|
// still a window, so the app keeps running and the tray stays the way back.
|
||||||
static void show_main_window() {
|
static void show_main_window() {
|
||||||
@@ -421,6 +438,19 @@ static gboolean on_minimize_watch(gpointer /*userData*/) {
|
|||||||
return G_SOURCE_CONTINUE;
|
return G_SOURCE_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A start from the session's autostart entry keeps the window out of the way, but
|
||||||
|
// only a hosted tray icon can bring it back - a desktop without one would leave the
|
||||||
|
// program with no window and no way to open one. Nothing knows that at startup
|
||||||
|
// (the panel answers asynchronously), so the window is built hidden and appears
|
||||||
|
// here if the icon did not turn up in the meantime.
|
||||||
|
static gboolean on_start_hidden_timeout(gpointer /*userData*/) {
|
||||||
|
if (!tray.ready()) {
|
||||||
|
g_printerr("gkeymapper: no tray icon, showing the window\n");
|
||||||
|
show_main_window();
|
||||||
|
}
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
|
||||||
// The panel is another process and cannot read the icon compiled into this
|
// The panel is another process and cannot read the icon compiled into this
|
||||||
// binary, so the tray icon travels as a pixmap. GdkPixbuf hands out RGBA, the
|
// binary, so the tray icon travels as a pixmap. GdkPixbuf hands out RGBA, the
|
||||||
// protocol wants ARGB32 in network byte order, so the bytes are reordered here.
|
// protocol wants ARGB32 in network byte order, so the bytes are reordered here.
|
||||||
@@ -478,7 +508,7 @@ static void start_tray(GtkApplication* app) {
|
|||||||
[app]() { g_application_quit(G_APPLICATION(app)); });
|
[app]() { g_application_quit(G_APPLICATION(app)); });
|
||||||
}
|
}
|
||||||
|
|
||||||
static void build_ui(GtkApplication* app);
|
static void build_ui(GtkApplication* app, bool show);
|
||||||
static void open_options_dialog();
|
static void open_options_dialog();
|
||||||
|
|
||||||
// Rebuilt from scratch when the language changes: the widgets keep the strings
|
// Rebuilt from scratch when the language changes: the widgets keep the strings
|
||||||
@@ -493,6 +523,9 @@ static gboolean rebuild_ui_idle(gpointer /*userData*/) {
|
|||||||
// were open, so nothing on screen is left in the old language.
|
// were open, so nothing on screen is left in the old language.
|
||||||
const bool helpOpen = gkeymapper::help_window_open();
|
const bool helpOpen = gkeymapper::help_window_open();
|
||||||
const bool optionsOpen = optionsDialog.is_open();
|
const bool optionsOpen = optionsDialog.is_open();
|
||||||
|
// A window that was in the tray (or was built hidden at login) stays there: the
|
||||||
|
// new one comes up the way the old one was, not in front of the user.
|
||||||
|
const bool windowShown = gtk_widget_get_visible(mainWindow);
|
||||||
// Rows that were not saved yet are widget state, not file content; without
|
// Rows that were not saved yet are widget state, not file content; without
|
||||||
// this the rebuilt window would silently show the last saved file again.
|
// this the rebuilt window would silently show the last saved file again.
|
||||||
const gkeymapper::MappingSet edits = gkeymapper::collect_mappings(rows);
|
const gkeymapper::MappingSet edits = gkeymapper::collect_mappings(rows);
|
||||||
@@ -508,7 +541,7 @@ static gboolean rebuild_ui_idle(gpointer /*userData*/) {
|
|||||||
gtk_window_destroy(GTK_WINDOW(mainWindow));
|
gtk_window_destroy(GTK_WINDOW(mainWindow));
|
||||||
mainWindow = nullptr;
|
mainWindow = nullptr;
|
||||||
|
|
||||||
build_ui(app);
|
build_ui(app, windowShown);
|
||||||
gkeymapper::apply_mappings(edits, rows);
|
gkeymapper::apply_mappings(edits, rows);
|
||||||
if (helpOpen) {
|
if (helpOpen) {
|
||||||
gkeymapper::show_help_window(GTK_WINDOW(mainWindow));
|
gkeymapper::show_help_window(GTK_WINDOW(mainWindow));
|
||||||
@@ -570,7 +603,7 @@ static void action_options(GSimpleAction* /*action*/, GVariant* /*parameter*/,
|
|||||||
open_options_dialog();
|
open_options_dialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
void build_ui(GtkApplication* app) {
|
void build_ui(GtkApplication* app, bool show) {
|
||||||
GtkWidget* window = gtk_application_window_new(app);
|
GtkWidget* window = gtk_application_window_new(app);
|
||||||
gtk_window_set_title(GTK_WINDOW(window), _("gKeyMapper – Logitech G910"));
|
gtk_window_set_title(GTK_WINDOW(window), _("gKeyMapper – Logitech G910"));
|
||||||
gtk_window_set_resizable(GTK_WINDOW(window), FALSE);
|
gtk_window_set_resizable(GTK_WINDOW(window), FALSE);
|
||||||
@@ -650,8 +683,16 @@ void build_ui(GtkApplication* app) {
|
|||||||
gtk_grid_attach(GTK_GRID(grid), h, c, 0, 1, 1);
|
gtk_grid_attach(GTK_GRID(grid), h, c, 0, 1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GTK draws the check mark of the selected entry right behind the text of a
|
||||||
|
// dropdown row, so every entry carries a trailing space. The catalogs keep
|
||||||
|
// the bare words; the space is added here, where the model is built.
|
||||||
const char* typeOptions[] = { _("Key"), _("Command"), _("Disabled"),
|
const char* typeOptions[] = { _("Key"), _("Command"), _("Disabled"),
|
||||||
_("Key (hold)"), nullptr };
|
_("Key (hold)"), nullptr };
|
||||||
|
g_autoptr(GtkStringList) typeModel = gtk_string_list_new(nullptr);
|
||||||
|
for (int i = 0; typeOptions[i] != nullptr; ++i) {
|
||||||
|
g_autofree gchar* shown = g_strconcat(typeOptions[i], " ", nullptr);
|
||||||
|
gtk_string_list_append(typeModel, shown);
|
||||||
|
}
|
||||||
|
|
||||||
rows.resize(kNumKeys);
|
rows.resize(kNumKeys);
|
||||||
for (int i = 0; i < kNumKeys; ++i) {
|
for (int i = 0; i < kNumKeys; ++i) {
|
||||||
@@ -665,7 +706,7 @@ void build_ui(GtkApplication* app) {
|
|||||||
rows[i].keyLabel = keyLabel;
|
rows[i].keyLabel = keyLabel;
|
||||||
|
|
||||||
// Type dropdown (fixed width so rows don't jump between languages)
|
// Type dropdown (fixed width so rows don't jump between languages)
|
||||||
GtkWidget* drop = gtk_drop_down_new_from_strings(typeOptions);
|
GtkWidget* drop = gtk_drop_down_new(G_LIST_MODEL(typeModel), nullptr);
|
||||||
gtk_drop_down_set_selected(GTK_DROP_DOWN(drop), 0);
|
gtk_drop_down_set_selected(GTK_DROP_DOWN(drop), 0);
|
||||||
gtk_widget_set_size_request(drop, 150, -1);
|
gtk_widget_set_size_request(drop, 150, -1);
|
||||||
gtk_grid_attach(GTK_GRID(grid), drop, 1, i + 1, 1, 1);
|
gtk_grid_attach(GTK_GRID(grid), drop, 1, i + 1, 1, 1);
|
||||||
@@ -691,13 +732,20 @@ void build_ui(GtkApplication* app) {
|
|||||||
update_row_state(&rows[i]);
|
update_row_state(&rows[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start from the saved mappings. Without this the rows are empty and the
|
// Start from the rows the replaced instance had on screen, or - when this is
|
||||||
// first "Save mappings" of a freshly opened window would overwrite the
|
// the only instance - from the saved mappings. Without either the rows are
|
||||||
// user's file with empty rows. A missing file is not an error (first start).
|
// empty and the first "Save mappings" of a freshly opened window would
|
||||||
|
// overwrite the user's file with empty rows; a missing file is not an error
|
||||||
|
// (first start). Taken once: after this the live rows are the source.
|
||||||
|
if (!handedOverRows.empty()) {
|
||||||
|
gkeymapper::apply_mappings_text(handedOverRows, rows);
|
||||||
|
handedOverRows.clear();
|
||||||
|
} else {
|
||||||
const std::string configPath = gkeymapper::default_config_path();
|
const std::string configPath = gkeymapper::default_config_path();
|
||||||
if (g_file_test(configPath.c_str(), G_FILE_TEST_EXISTS)) {
|
if (g_file_test(configPath.c_str(), G_FILE_TEST_EXISTS)) {
|
||||||
gkeymapper::read_mappings(configPath, rows);
|
gkeymapper::read_mappings(configPath, rows);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Footer with save button and the daemon status line
|
// Footer with save button and the daemon status line
|
||||||
GtkWidget* footer = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
|
GtkWidget* footer = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
|
||||||
@@ -735,10 +783,18 @@ void build_ui(GtkApplication* app) {
|
|||||||
gtk_box_append(GTK_BOX(footer), daemonLabel);
|
gtk_box_append(GTK_BOX(footer), daemonLabel);
|
||||||
|
|
||||||
mainWindow = window;
|
mainWindow = window;
|
||||||
|
// The type dropdowns and the header bar menu are popovers; without this they
|
||||||
|
// grab the pointer and freeze the rest of the desktop while they are open.
|
||||||
|
gkeymapper::make_popovers_release_input(GTK_WINDOW(window));
|
||||||
// Closing is a request, not the end: the handler decides whether the window
|
// Closing is a request, not the end: the handler decides whether the window
|
||||||
// hides to the tray or the app quits with it.
|
// hides to the tray or the app quits with it.
|
||||||
g_signal_connect(window, "close-request", G_CALLBACK(on_close_request), nullptr);
|
g_signal_connect(window, "close-request", G_CALLBACK(on_close_request), nullptr);
|
||||||
|
// `show` is false for the one start that must not put a window in the way: the
|
||||||
|
// one the session's autostart entry makes (on_start_hidden_timeout is what then
|
||||||
|
// decides whether it has to appear after all).
|
||||||
|
if (show) {
|
||||||
gtk_window_present(GTK_WINDOW(window));
|
gtk_window_present(GTK_WINDOW(window));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void on_activate(GtkApplication* app, gpointer /*user_data*/) {
|
static void on_activate(GtkApplication* app, gpointer /*user_data*/) {
|
||||||
@@ -780,12 +836,27 @@ static void on_activate(GtkApplication* app, gpointer /*user_data*/) {
|
|||||||
const char* quitAccels[] = {"<Control>q", NULL};
|
const char* quitAccels[] = {"<Control>q", NULL};
|
||||||
gtk_application_set_accels_for_action(app, "app.quit", quitAccels);
|
gtk_application_set_accels_for_action(app, "app.quit", quitAccels);
|
||||||
|
|
||||||
build_ui(app);
|
// A start from the session leaves the window in the tray; a start by hand shows
|
||||||
|
// it (main() reads the flag off the command line).
|
||||||
|
build_ui(app, !startHidden);
|
||||||
|
|
||||||
|
// From here on this instance answers a new start with the rows its window
|
||||||
|
// shows, so the instance that replaces it does not lose edits that were never
|
||||||
|
// saved (see replace_running_instance). It has to come after build_ui(), which
|
||||||
|
// is what fills the rows.
|
||||||
|
gkeymapper::serve_rows_handover([]() { return gkeymapper::mappings_text(rows); });
|
||||||
|
|
||||||
// One-time, and deliberately not part of build_ui(): the window is rebuilt
|
// One-time, and deliberately not part of build_ui(): the window is rebuilt
|
||||||
// when the language changes, while the tray, its poll and the daemon client
|
// when the language changes, while the tray, its poll and the daemon client
|
||||||
// keep running as they are.
|
// keep running as they are.
|
||||||
start_tray(app);
|
start_tray(app);
|
||||||
|
if (startHidden) {
|
||||||
|
// The tray is what keeps a hidden window reachable; is it one? Only the
|
||||||
|
// panel can answer that, and it does so asynchronously (start_tray above is
|
||||||
|
// the question). Until then the window stays hidden and comes up if that
|
||||||
|
// question was never answered.
|
||||||
|
g_timeout_add(kStartHiddenGraceMs, on_start_hidden_timeout, nullptr);
|
||||||
|
}
|
||||||
// Four checks per second: one cheap call into the backend each, quick enough
|
// Four checks per second: one cheap call into the backend each, quick enough
|
||||||
// that a minimized window does not linger in the task list. It does nothing
|
// that a minimized window does not linger in the task list. It does nothing
|
||||||
// while the tray option is off.
|
// while the tray option is off.
|
||||||
@@ -813,16 +884,32 @@ int main(int argc, char** argv) {
|
|||||||
// the first translated string is built.
|
// the first translated string is built.
|
||||||
gkeymapper::apply_gui_language(guiSettings.language);
|
gkeymapper::apply_gui_language(guiSettings.language);
|
||||||
|
|
||||||
|
// The autostart entry starts the window in the tray and says so with an argument
|
||||||
|
// of its own (kStartHiddenArg). GTK does not know that argument and would stop
|
||||||
|
// on it, so it is taken out of the command line here, before it sees it.
|
||||||
|
std::vector<char*> args;
|
||||||
|
args.reserve(static_cast<size_t>(argc) + 1);
|
||||||
|
for (int i = 0; i < argc; ++i) {
|
||||||
|
if (g_strcmp0(argv[i], gkeymapper::kStartHiddenArg) == 0) {
|
||||||
|
startHidden = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
args.push_back(argv[i]);
|
||||||
|
}
|
||||||
|
args.push_back(nullptr);
|
||||||
|
|
||||||
// One GUI only, and it is the one that was just started: close the instance
|
// One GUI only, and it is the one that was just started: close the instance
|
||||||
// that already owns the app id (an older build, or the same one) before
|
// that already owns the app id (an older build, or the same one) before
|
||||||
// becoming the primary instance. Without this the new process just hands over
|
// becoming the primary instance. Without this the new process just hands over
|
||||||
// to the old one and its stale window stays on screen.
|
// to the old one and its stale window stays on screen. Its rows come along,
|
||||||
gkeymapper::replace_running_instance(gkeymapper::ipc::kAppId);
|
// so a mapping that was typed but not saved is not lost with it.
|
||||||
|
gkeymapper::replace_running_instance(gkeymapper::ipc::kAppId, &handedOverRows);
|
||||||
|
|
||||||
GtkApplication* app = gtk_application_new(gkeymapper::ipc::kAppId,
|
GtkApplication* app = gtk_application_new(gkeymapper::ipc::kAppId,
|
||||||
G_APPLICATION_FLAGS_NONE);
|
G_APPLICATION_FLAGS_NONE);
|
||||||
g_signal_connect(app, "activate", G_CALLBACK(on_activate), nullptr);
|
g_signal_connect(app, "activate", G_CALLBACK(on_activate), nullptr);
|
||||||
int status = g_application_run(G_APPLICATION(app), argc, argv);
|
int status = g_application_run(G_APPLICATION(app), static_cast<int>(args.size()) - 1,
|
||||||
|
args.data());
|
||||||
g_object_unref(app);
|
g_object_unref(app);
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,4 +63,12 @@ void apply_mappings(const MappingSet& set, const std::vector<KeyRow>& rows) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string mappings_text(const std::vector<KeyRow>& rows) {
|
||||||
|
return format_mappings(collect_mappings(rows));
|
||||||
|
}
|
||||||
|
|
||||||
|
void apply_mappings_text(const std::string& text, const std::vector<KeyRow>& rows) {
|
||||||
|
apply_mappings(parse_mappings(text), rows);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace gkeymapper
|
} // namespace gkeymapper
|
||||||
|
|||||||
@@ -17,6 +17,13 @@ namespace gkeymapper {
|
|||||||
MappingSet collect_mappings(const std::vector<KeyRow>& rows);
|
MappingSet collect_mappings(const std::vector<KeyRow>& rows);
|
||||||
void apply_mappings(const MappingSet& set, const std::vector<KeyRow>& rows);
|
void apply_mappings(const MappingSet& set, const std::vector<KeyRow>& rows);
|
||||||
|
|
||||||
|
// The same round trip as text (the mappings file format), for rows that have to
|
||||||
|
// leave the process: the window that replaces a running instance is given them by
|
||||||
|
// it (see src/singleinstance), so edits that were never saved survive a second
|
||||||
|
// start as well.
|
||||||
|
std::string mappings_text(const std::vector<KeyRow>& rows);
|
||||||
|
void apply_mappings_text(const std::string& text, const std::vector<KeyRow>& rows);
|
||||||
|
|
||||||
// Collects the current widget state and writes it to path.
|
// Collects the current widget state and writes it to path.
|
||||||
bool write_mappings(const std::string& path, const std::vector<KeyRow>& rows);
|
bool write_mappings(const std::string& path, const std::vector<KeyRow>& rows);
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
#include <glib/gi18n.h>
|
#include <glib/gi18n.h>
|
||||||
|
|
||||||
|
#include "popovers.h"
|
||||||
|
|
||||||
namespace gkeymapper {
|
namespace gkeymapper {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -119,19 +121,30 @@ void OptionsDialog::show(GtkWindow* parent, const OptionsState& state, ToggleFn
|
|||||||
gtk_box_append(GTK_BOX(box), autostartRow);
|
gtk_box_append(GTK_BOX(box), autostartRow);
|
||||||
// Static text, so unlike the tray hint it needs no member: the switch is
|
// Static text, so unlike the tray hint it needs no member: the switch is
|
||||||
// what changes.
|
// what changes.
|
||||||
GtkWidget* autostartHint = hint_label(_("Opens this window when you log in. Not needed "
|
GtkWidget* autostartHint = hint_label(_("Opens this window minimized to the tray when you "
|
||||||
"for the G keys - the daemon does the work and "
|
"log in. Not needed for the G keys - the daemon "
|
||||||
"is the only part that has to run."));
|
"does the work and is the only part that has to "
|
||||||
|
"run."));
|
||||||
gtk_box_append(GTK_BOX(box), autostartHint);
|
gtk_box_append(GTK_BOX(box), autostartHint);
|
||||||
|
|
||||||
gtk_box_append(GTK_BOX(box), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL));
|
gtk_box_append(GTK_BOX(box), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL));
|
||||||
gtk_box_append(GTK_BOX(box), section_label(_("Language")));
|
gtk_box_append(GTK_BOX(box), section_label(_("Language")));
|
||||||
// GtkDropDown has no label of its own; the heading above it says what it
|
// GtkDropDown has no label of its own; the heading above it says what it
|
||||||
// is, and the accessible name says it again for screen readers.
|
// is, and the accessible name says it again for screen readers.
|
||||||
|
|
||||||
|
// Trailing space like the type dropdown in the main window: GTK draws the
|
||||||
|
// check mark of the selected entry right behind the text.
|
||||||
const char* languageOptions[] = { _("System language"), "Deutsch", "English", "Español",
|
const char* languageOptions[] = { _("System language"), "Deutsch", "English", "Español",
|
||||||
"Français", nullptr };
|
"Français", nullptr };
|
||||||
languageDrop_ = gtk_drop_down_new_from_strings(languageOptions);
|
g_autoptr(GtkStringList) languageModel = gtk_string_list_new(nullptr);
|
||||||
gtk_widget_set_halign(languageDrop_, GTK_ALIGN_START);
|
for (int i = 0; languageOptions[i] != nullptr; ++i) {
|
||||||
|
g_autofree gchar* shown = g_strconcat(languageOptions[i], " ", nullptr);
|
||||||
|
gtk_string_list_append(languageModel, shown);
|
||||||
|
}
|
||||||
|
languageDrop_ = gtk_drop_down_new(G_LIST_MODEL(languageModel), nullptr);
|
||||||
|
// Right-aligned like the switches above: the GUI's own controls sit on one
|
||||||
|
// line along the right edge, the text explains them on the left.
|
||||||
|
gtk_widget_set_halign(languageDrop_, GTK_ALIGN_END);
|
||||||
gtk_accessible_update_property(GTK_ACCESSIBLE(languageDrop_),
|
gtk_accessible_update_property(GTK_ACCESSIBLE(languageDrop_),
|
||||||
GTK_ACCESSIBLE_PROPERTY_LABEL, _("Language"), -1);
|
GTK_ACCESSIBLE_PROPERTY_LABEL, _("Language"), -1);
|
||||||
g_signal_connect(languageDrop_, "notify::selected", G_CALLBACK(on_language_changed), this);
|
g_signal_connect(languageDrop_, "notify::selected", G_CALLBACK(on_language_changed), this);
|
||||||
@@ -168,6 +181,9 @@ void OptionsDialog::show(GtkWindow* parent, const OptionsState& state, ToggleFn
|
|||||||
gtk_box_append(GTK_BOX(box), closeButton);
|
gtk_box_append(GTK_BOX(box), closeButton);
|
||||||
|
|
||||||
g_signal_connect(window_, "destroy", G_CALLBACK(on_destroy), this);
|
g_signal_connect(window_, "destroy", G_CALLBACK(on_destroy), this);
|
||||||
|
// The language dropdown is a popover; without this it grabs the pointer
|
||||||
|
// and freezes the rest of the desktop while it is open.
|
||||||
|
make_popovers_release_input(GTK_WINDOW(window_));
|
||||||
}
|
}
|
||||||
|
|
||||||
apply();
|
apply();
|
||||||
|
|||||||
144
src/popovers.cpp
Normal file
144
src/popovers.cpp
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
// gKeyMapper - popup handling that GTK gets wrong in this version.
|
||||||
|
//
|
||||||
|
// GTK 4.6's popovers - the list a dropdown opens and the header bar menu - grab
|
||||||
|
// the pointer while they are open. An X11 grab swallows every click of the
|
||||||
|
// other clients, and this GTK does not close the popup on a click outside the
|
||||||
|
// application either: with a dropdown open, nothing on the desktop can be
|
||||||
|
// clicked any more, neither another window nor the desktop itself, until Escape
|
||||||
|
// is pressed. GTK 3 replayed such a click to the window under the pointer, GTK 4
|
||||||
|
// dropped that. A popup that is left open is not the problem; a desktop that is
|
||||||
|
// frozen is.
|
||||||
|
//
|
||||||
|
// A popover only grabs while its "autohide" is on, so switching that off hands
|
||||||
|
// the clicks back to the rest of the desktop. The popups are then closed here,
|
||||||
|
// which is the part that autohide would have done:
|
||||||
|
// - a click in the window, outside the popup: on the press, so the click still
|
||||||
|
// reaches the widget under the pointer (a previous popup must not eat it),
|
||||||
|
// - a click on the widget that opened it: left alone, its own toggle closes it,
|
||||||
|
// - a click in the popup: left alone, it picks the entry and closes the popup,
|
||||||
|
// - a click anywhere else (another program, the desktop): the window loses the
|
||||||
|
// focus, and that is what closes it here - the click itself goes to whatever
|
||||||
|
// was clicked, which is the whole point,
|
||||||
|
// - Escape.
|
||||||
|
|
||||||
|
#include "popovers.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace gkeymapper {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
void collect_popovers(GtkWidget* widget, std::vector<GtkWidget*>* popovers) {
|
||||||
|
for (GtkWidget* child = gtk_widget_get_first_child(widget); child != nullptr;
|
||||||
|
child = gtk_widget_get_next_sibling(child)) {
|
||||||
|
if (GTK_IS_POPOVER(child)) {
|
||||||
|
popovers->push_back(child);
|
||||||
|
}
|
||||||
|
collect_popovers(child, popovers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void popovers_of(GtkWindow* window, std::vector<GtkWidget*>* popovers) {
|
||||||
|
collect_popovers(GTK_WIDGET(window), popovers);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A press in the popup itself is a choice and must reach the entry under the
|
||||||
|
// pointer. GTK hands such a press to the popup's own surface, so this controller
|
||||||
|
// normally does not see it at all - the check is a guard for the case where it
|
||||||
|
// does, and it has to be by surface and not by position, because the coordinates
|
||||||
|
// of that press are the popup's and not the window's.
|
||||||
|
bool press_on_popup(GdkEvent* event, GtkWidget* popover) {
|
||||||
|
GdkSurface* popup = gtk_native_get_surface(GTK_NATIVE(popover));
|
||||||
|
return popup != nullptr && gdk_event_get_surface(event) == popup;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool press_inside(GtkWidget* widget, GtkWindow* window, double x, double y) {
|
||||||
|
graphene_rect_t rect;
|
||||||
|
if (widget == nullptr || !gtk_widget_compute_bounds(widget, GTK_WIDGET(window), &rect)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return x >= rect.origin.x && x <= rect.origin.x + rect.size.width && y >= rect.origin.y &&
|
||||||
|
y <= rect.origin.y + rect.size.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
void close_visible_popovers(GtkWindow* window) {
|
||||||
|
std::vector<GtkWidget*> popovers;
|
||||||
|
popovers_of(window, &popovers);
|
||||||
|
for (GtkWidget* popover : popovers) {
|
||||||
|
if (gtk_widget_get_visible(popover)) {
|
||||||
|
gtk_popover_popdown(GTK_POPOVER(popover));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean on_event(GtkEventControllerLegacy* /*controller*/, GdkEvent* event, gpointer userData) {
|
||||||
|
GtkWindow* window = GTK_WINDOW(userData);
|
||||||
|
const GdkEventType type = gdk_event_get_event_type(event);
|
||||||
|
if (type != GDK_BUTTON_PRESS && type != GDK_KEY_PRESS) {
|
||||||
|
return GDK_EVENT_PROPAGATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<GtkWidget*> popovers;
|
||||||
|
popovers_of(window, &popovers);
|
||||||
|
for (GtkWidget* popover : popovers) {
|
||||||
|
// A popover GTK made after this was installed must not grab either; the
|
||||||
|
// press that opens it is seen here before the widget acts on it, and
|
||||||
|
// switching a visible one off drops the grab right away.
|
||||||
|
gtk_popover_set_autohide(GTK_POPOVER(popover), FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == GDK_KEY_PRESS) {
|
||||||
|
if (g_strcmp0(gdk_keyval_name(gdk_key_event_get_keyval(event)), "Escape") == 0) {
|
||||||
|
for (GtkWidget* popover : popovers) {
|
||||||
|
if (gtk_widget_get_visible(popover)) {
|
||||||
|
gtk_popover_popdown(GTK_POPOVER(popover));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return GDK_EVENT_PROPAGATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
double x = 0;
|
||||||
|
double y = 0;
|
||||||
|
gdk_event_get_position(event, &x, &y);
|
||||||
|
for (GtkWidget* popover : popovers) {
|
||||||
|
if (!gtk_widget_get_visible(popover) || press_on_popup(event, popover)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// The widget that opened the popup sits in the window, so the position is
|
||||||
|
// in the window's coordinates here.
|
||||||
|
if (press_inside(gtk_widget_get_parent(popover), window, x, y)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
gtk_popover_popdown(GTK_POPOVER(popover));
|
||||||
|
}
|
||||||
|
return GDK_EVENT_PROPAGATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_focus_leave(GtkEventControllerFocus* /*controller*/, gpointer userData) {
|
||||||
|
close_visible_popovers(GTK_WINDOW(userData));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void make_popovers_release_input(GtkWindow* window) {
|
||||||
|
std::vector<GtkWidget*> popovers;
|
||||||
|
popovers_of(window, &popovers);
|
||||||
|
for (GtkWidget* popover : popovers) {
|
||||||
|
gtk_popover_set_autohide(GTK_POPOVER(popover), FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A legacy controller does not claim the events it sees, so the widgets keep
|
||||||
|
// behaving as usual; the capture phase is what makes it see a press before
|
||||||
|
// the widget under the pointer does anything with it.
|
||||||
|
GtkEventController* events = gtk_event_controller_legacy_new();
|
||||||
|
gtk_event_controller_set_propagation_phase(events, GTK_PHASE_CAPTURE);
|
||||||
|
g_signal_connect(events, "event", G_CALLBACK(on_event), window);
|
||||||
|
gtk_widget_add_controller(GTK_WIDGET(window), events);
|
||||||
|
|
||||||
|
GtkEventController* focus = gtk_event_controller_focus_new();
|
||||||
|
g_signal_connect(focus, "leave", G_CALLBACK(on_focus_leave), window);
|
||||||
|
gtk_widget_add_controller(GTK_WIDGET(window), focus);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace gkeymapper
|
||||||
18
src/popovers.h
Normal file
18
src/popovers.h
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
// gKeyMapper - popup handling that GTK gets wrong in this version.
|
||||||
|
//
|
||||||
|
// See popovers.cpp for why this exists; in short, a popover that keeps its
|
||||||
|
// input grab makes the rest of the desktop unclickable on X11.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
namespace gkeymapper {
|
||||||
|
|
||||||
|
// Call once per toplevel window, after the widgets are built. Every popover in
|
||||||
|
// the window (the dropdowns' popup lists, the header bar menu) then lets the
|
||||||
|
// pointer go, and is closed by us instead - outside click, Escape, or the
|
||||||
|
// window losing the focus.
|
||||||
|
void make_popovers_release_input(GtkWindow* window);
|
||||||
|
|
||||||
|
} // namespace gkeymapper
|
||||||
@@ -3,16 +3,20 @@
|
|||||||
// GtkApplication already makes a second process hand over to the first one (the
|
// GtkApplication already makes a second process hand over to the first one (the
|
||||||
// app id is unique), but that leaves the *old* window on screen - including one
|
// app id is unique), but that leaves the *old* window on screen - including one
|
||||||
// from an earlier build. So main() first closes what is running and only then
|
// from an earlier build. So main() first closes what is running and only then
|
||||||
// becomes the primary instance itself.
|
// becomes the primary instance itself. What it takes along are the rows that
|
||||||
|
// window shows: the takeover would otherwise drop edits that were never saved.
|
||||||
|
|
||||||
#include "singleinstance.h"
|
#include "singleinstance.h"
|
||||||
|
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include <gio/gio.h>
|
#include <gio/gio.h>
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "ipc_ids.h"
|
||||||
|
|
||||||
namespace gkeymapper {
|
namespace gkeymapper {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -22,6 +26,9 @@ constexpr int kQuitWaitMs = 1500;
|
|||||||
constexpr int kTermWaitMs = 1500;
|
constexpr int kTermWaitMs = 1500;
|
||||||
constexpr int kPollMs = 50;
|
constexpr int kPollMs = 50;
|
||||||
|
|
||||||
|
// Asked by the next start (see serve_rows_handover).
|
||||||
|
RowsProvider gRowsProvider;
|
||||||
|
|
||||||
GDBusConnection* session_bus() {
|
GDBusConnection* session_bus() {
|
||||||
GError* error = nullptr;
|
GError* error = nullptr;
|
||||||
GDBusConnection* bus = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, &error);
|
GDBusConnection* bus = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, &error);
|
||||||
@@ -94,6 +101,33 @@ bool ask_to_quit(GDBusConnection* bus, const std::string& name) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The rows of the instance that is running, fetched before it goes away. No answer
|
||||||
|
// (an older build has no such method) simply leaves the new window with the file.
|
||||||
|
bool ask_for_rows(GDBusConnection* bus, const std::string& name, std::string& out) {
|
||||||
|
GVariant* reply = call(bus, name.c_str(), ipc::kWindowObjectPath, ipc::kWindowInterface,
|
||||||
|
ipc::kWindowRowsMethod, nullptr, G_VARIANT_TYPE("(s)"));
|
||||||
|
if (reply == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const gchar* text = nullptr;
|
||||||
|
g_variant_get(reply, "(&s)", &text);
|
||||||
|
out = text != nullptr ? text : "";
|
||||||
|
g_variant_unref(reply);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Answers the next start. This runs on the main loop, which is the thread that
|
||||||
|
// owns the widgets, so the provider may read them.
|
||||||
|
void on_get_rows(GDBusConnection* /*connection*/, const gchar* /*sender*/,
|
||||||
|
const gchar* /*objectPath*/, const gchar* /*interfaceName*/,
|
||||||
|
const gchar* /*methodName*/, GVariant* /*parameters*/,
|
||||||
|
GDBusMethodInvocation* invocation, gpointer /*userData*/) {
|
||||||
|
const std::string rows = gRowsProvider ? gRowsProvider() : std::string();
|
||||||
|
g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", rows.c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
const GDBusInterfaceVTable kHandoverVTable = {on_get_rows, nullptr, nullptr, {}};
|
||||||
|
|
||||||
pid_t owner_pid(GDBusConnection* bus, const std::string& name) {
|
pid_t owner_pid(GDBusConnection* bus, const std::string& name) {
|
||||||
GVariant* reply = call(bus, "org.freedesktop.DBus", "/org/freedesktop/DBus",
|
GVariant* reply = call(bus, "org.freedesktop.DBus", "/org/freedesktop/DBus",
|
||||||
"org.freedesktop.DBus", "GetConnectionUnixProcessID",
|
"org.freedesktop.DBus", "GetConnectionUnixProcessID",
|
||||||
@@ -124,7 +158,32 @@ bool is_our_gui(pid_t pid) {
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
bool replace_running_instance(const std::string& appId) {
|
void serve_rows_handover(RowsProvider provider) {
|
||||||
|
gRowsProvider = std::move(provider);
|
||||||
|
|
||||||
|
GDBusConnection* bus = session_bus();
|
||||||
|
if (bus == nullptr) {
|
||||||
|
return; // without a bus nobody can ask
|
||||||
|
}
|
||||||
|
// Built from the ids, so the XML and the constants cannot drift apart.
|
||||||
|
g_autofree gchar* xml = g_strdup_printf(
|
||||||
|
"<node><interface name='%s'><method name='%s'>"
|
||||||
|
"<arg type='s' name='rows' direction='out'/></method></interface></node>",
|
||||||
|
ipc::kWindowInterface, ipc::kWindowRowsMethod);
|
||||||
|
GError* error = nullptr;
|
||||||
|
GDBusNodeInfo* info = g_dbus_node_info_new_for_xml(xml, &error);
|
||||||
|
if (info != nullptr) {
|
||||||
|
// The registration lives as long as the process; being called twice would
|
||||||
|
// only report "already registered", which is not worth a message.
|
||||||
|
g_dbus_connection_register_object(bus, ipc::kWindowObjectPath, info->interfaces[0],
|
||||||
|
&kHandoverVTable, nullptr, nullptr, &error);
|
||||||
|
g_dbus_node_info_unref(info);
|
||||||
|
}
|
||||||
|
g_clear_error(&error);
|
||||||
|
g_object_unref(bus);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool replace_running_instance(const std::string& appId, std::string* rowsOut) {
|
||||||
GDBusConnection* bus = session_bus();
|
GDBusConnection* bus = session_bus();
|
||||||
if (bus == nullptr) {
|
if (bus == nullptr) {
|
||||||
return true; // without a bus there is no other instance either
|
return true; // without a bus there is no other instance either
|
||||||
@@ -132,6 +191,11 @@ bool replace_running_instance(const std::string& appId) {
|
|||||||
|
|
||||||
bool free = !has_owner(bus, appId);
|
bool free = !has_owner(bus, appId);
|
||||||
if (!free) {
|
if (!free) {
|
||||||
|
// The rows first, then the quit: after it there is nobody left to ask.
|
||||||
|
if (rowsOut != nullptr) {
|
||||||
|
ask_for_rows(bus, appId, *rowsOut);
|
||||||
|
}
|
||||||
|
|
||||||
// Clean path: the running instance quits itself.
|
// Clean path: the running instance quits itself.
|
||||||
ask_to_quit(bus, appId);
|
ask_to_quit(bus, appId);
|
||||||
free = wait_until_free(bus, appId, kQuitWaitMs);
|
free = wait_until_free(bus, appId, kQuitWaitMs);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#ifndef GKEYMAPPER_SINGLEINSTANCE_H
|
#ifndef GKEYMAPPER_SINGLEINSTANCE_H
|
||||||
#define GKEYMAPPER_SINGLEINSTANCE_H
|
#define GKEYMAPPER_SINGLEINSTANCE_H
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace gkeymapper {
|
namespace gkeymapper {
|
||||||
@@ -13,10 +14,25 @@ namespace gkeymapper {
|
|||||||
// terminated with SIGTERM instead, after checking that the bus name really
|
// terminated with SIGTERM instead, after checking that the bus name really
|
||||||
// belongs to a gkeymapper process.
|
// belongs to a gkeymapper process.
|
||||||
//
|
//
|
||||||
|
// Before the quit it asks that instance for the rows its window shows and puts
|
||||||
|
// them into `rowsOut` (the mappings text, the same format as the file). A new
|
||||||
|
// window starts from that instead of from the file, so edits that were never
|
||||||
|
// saved are not lost with the replaced window. An instance that does not answer
|
||||||
|
// (an older build) leaves `rowsOut` alone; pass nullptr to skip the asking.
|
||||||
|
//
|
||||||
// Returns true when the name is free afterwards, which is also the case when
|
// Returns true when the name is free afterwards, which is also the case when
|
||||||
// nobody owned it. The caller still runs its usual single-instance logic, so a
|
// nobody owned it. The caller still runs its usual single-instance logic, so a
|
||||||
// `false` (a hung instance that cannot be closed) only means "no new window".
|
// `false` (a hung instance that cannot be closed) only means "no new window".
|
||||||
bool replace_running_instance(const std::string& appId);
|
bool replace_running_instance(const std::string& appId, std::string* rowsOut = nullptr);
|
||||||
|
|
||||||
|
// The rows of this window, as `mappings_text` produces them.
|
||||||
|
using RowsProvider = std::function<std::string()>;
|
||||||
|
|
||||||
|
// The other side of the above: answers a new start that asks for the rows. Call it
|
||||||
|
// once the window exists; `provider` runs on this process's main loop, so reading
|
||||||
|
// the widgets is safe. Registering twice keeps the second provider, which only
|
||||||
|
// matters if a start could reach here more than once.
|
||||||
|
void serve_rows_handover(RowsProvider provider);
|
||||||
|
|
||||||
} // namespace gkeymapper
|
} // namespace gkeymapper
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user