feat: relibc S1 — sem_open refcounting + glibc cross-reference assessment
Phase S1 (Critical Correctness): - sem_open/sem_close: global refcounting via BTreeMap + AtomicUsize - sem_close: decrements refcount, munmaps only at zero - sem_open: reuses existing mapping, O_EXCL returns EEXIST - sem_unlink: marks entry for removal before shm_unlink - va_list parsing: reads mode_t and value from stack after oflag - All 11 sem_* functions verified in libc.so T Phase S2-S4 (Designed, documented): - eventfd() function, signalfd read path, EINTR handling - name canonicalization, cancellation safety - Full plan in local/docs/RELIBC-AGAINST-GLIBC-ASSESSMENT.md Reference: glibc 2.41 cloned to local/reference/glibc/ Boot verified: greeter ready on VT 3 with refcounted semaphores
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
# Relibc vs GNU libc — Cross-Reference Assessment
|
||||
|
||||
**Date:** 2026-05-05
|
||||
**Reference:** glibc 2.41 (2026-05-05 clone from sourceware.org)
|
||||
**Relibc pinned:** commit 861bbb0 with Red Bear patch chain (26 patches)
|
||||
|
||||
---
|
||||
|
||||
## 1. eventfd
|
||||
|
||||
### glibc reference
|
||||
|
||||
```c
|
||||
// sysdeps/unix/sysv/linux/eventfd.c (not cloned yet — syscall wrapper)
|
||||
// bits/eventfd.h:
|
||||
EFD_SEMAPHORE = 00000001 // octal 1
|
||||
EFD_CLOEXEC = 02000000 // octal 0x80000
|
||||
EFD_NONBLOCK = 00004000 // octal 0x800
|
||||
```
|
||||
|
||||
glibc calls `INLINE_SYSCALL(eventfd2, 2, initval, flags)` — a kernel syscall. The kernel creates an anonymous file descriptor for event notification. Supports `EFD_SEMAPHORE` (semaphore-like counting), `EFD_CLOEXEC`, `EFD_NONBLOCK`.
|
||||
|
||||
### relibc current state
|
||||
|
||||
```rust
|
||||
// src/header/sys_eventfd/mod.rs — 8 lines
|
||||
pub const EFD_SEMAPHORE: c_int = 1;
|
||||
pub const EFD_CLOEXEC: c_int = 0x80000;
|
||||
pub const EFD_NONBLOCK: c_int = 0x800;
|
||||
// No eventfd() function — constants only
|
||||
```
|
||||
|
||||
**Constants match** ✅
|
||||
**No implementation** ❌ — libwayland provides inline `eventfd()` via `/scheme/event`
|
||||
|
||||
### Gaps
|
||||
|
||||
| Gap | Severity | Detail |
|
||||
|-----|----------|--------|
|
||||
| No `eventfd()` in relibc | Medium | libwayland has its own inline, but relibc should be canonical |
|
||||
| No `eventfd_read()`/`eventfd_write()` | Low | POSIX-adjacent convenience wrappers (glibc provides them) |
|
||||
|
||||
---
|
||||
|
||||
## 2. signalfd
|
||||
|
||||
### glibc reference
|
||||
|
||||
```c
|
||||
// sysdeps/unix/sysv/linux/signalfd.c
|
||||
int signalfd(int fd, const sigset_t *mask, int flags) {
|
||||
return INLINE_SYSCALL(signalfd4, 4, fd, mask, __NSIG_BYTES, flags);
|
||||
}
|
||||
|
||||
// bits/signalfd.h:
|
||||
SFD_CLOEXEC = 02000000 // octal 0x80000
|
||||
SFD_NONBLOCK = 00004000 // octal 0x800
|
||||
```
|
||||
|
||||
glibc is a thin syscall wrapper. Kernel handles signal mask, fd management, and non-blocking reads returning `struct signalfd_siginfo`.
|
||||
|
||||
### relibc current state
|
||||
|
||||
```rust
|
||||
// src/header/signal/signalfd.rs — 103 lines
|
||||
// Full implementation: opens /scheme/event, applies CLOEXEC/NONBLOCK via fcntl,
|
||||
// calls sigprocmask(SIG_BLOCK, mask), returns fd.
|
||||
// signalfd4 supports modifying existing fd's flags.
|
||||
```
|
||||
|
||||
**Flags match glibc** ✅
|
||||
**signalfd_siginfo struct matches** ✅
|
||||
**signalfd4 with existing fd** ✅ (fcntl-based flag modification)
|
||||
|
||||
### Prowess vs glibc
|
||||
|
||||
| Aspect | glibc | relibc |
|
||||
|--------|-------|--------|
|
||||
| Implementation | Syscall wrapper (5 lines) | Userspace via `/scheme/event` (100 lines) |
|
||||
| Existing fd support | Kernel handles | fcntl O_CLOEXEC/O_NONBLOCK modification ✅ |
|
||||
| Errno mapping | Kernel errno | Wraps in Errno, proper EINVAL/EFAULT |
|
||||
| Signal blocking | Kernel auto-blocks on read | `sigprocmask(SIG_BLOCK, mask)` called explicitly ✅ |
|
||||
|
||||
### Gaps
|
||||
|
||||
| Gap | Severity | Detail |
|
||||
|-----|----------|--------|
|
||||
| No read path | High | Nothing reads `signalfd_siginfo` from the fd — the `/scheme/event` fd is opened but signals aren't delivered through it |
|
||||
| Signal delivery unverified | High | The `sigprocmask(SIG_BLOCK)` blocks signals but there's no evidence the kernel delivers them via the event fd |
|
||||
| `signalfd_siginfo` read not implemented | Critical | `struct signalfd_siginfo` is defined but never populated via read(2) |
|
||||
|
||||
---
|
||||
|
||||
## 3. Semaphores
|
||||
|
||||
### glibc reference
|
||||
|
||||
```c
|
||||
// sysdeps/pthread/sem_open.c — 216 lines
|
||||
// Uses:
|
||||
// __shm_get_name(name) → canonical path in /dev/shm
|
||||
// O_CREAT+O_EXCL path: creates temp file, writes semaphore header, ftruncate to sizeof(sem_t), mmap
|
||||
// Non-create path: open existing, __sem_check_add_mapping(name, fd) → reuse or mmap
|
||||
// pthread_setcancelstate(PTHREAD_CANCEL_DISABLE) — cancellation-safe
|
||||
// va_arg for mode_t when O_CREAT
|
||||
```
|
||||
|
||||
glibc uses a sophisticated named semaphore implementation:
|
||||
1. **Name canonicalization**: `__shm_get_name` transforms `/name` → `/dev/shm/sem.name`
|
||||
2. **Existing mapping reuse**: `__sem_check_add_mapping` checks global list of already-mapped semaphores
|
||||
3. **Atomic creation**: O_CREAT+O_EXCL with temp file, then writes header, ftruncate, mmap
|
||||
4. **Cancellation safety**: `pthread_setcancelstate(PTHREAD_CANCEL_DISABLE)` around file operations
|
||||
5. **Proper mode_t**: va_arg for mode when O_CREAT
|
||||
6. **Reference counting**: `__sem_check_add_mapping` increments refcount, `sem_close` decrements
|
||||
|
||||
### relibc current state
|
||||
|
||||
```rust
|
||||
// src/header/semaphore/mod.rs — 176 lines
|
||||
// Uses: shm_open(name, O_CREAT|O_EXCL|O_RDWR, mode) → ftruncate → mmap → init
|
||||
// NamedSemaphore struct with RlctSempahore (futex-based)
|
||||
```
|
||||
|
||||
**Core mechanism works** ✅ (shm_open + mmap)
|
||||
**sem_init/destroy/post/wait/trywait/timedwait/clockwait** ✅
|
||||
**sem_open/close/unlink** ✅ (implemented in P3-semaphore-comprehensive.patch)
|
||||
|
||||
### Gaps vs glibc
|
||||
|
||||
| Gap | Severity | Detail |
|
||||
|-----|----------|--------|
|
||||
| **No name canonicalization** | Medium | Names go directly to `shm_open` without prefix/suffix. glibc uses `/dev/shm/sem.NAME` |
|
||||
| **No existing mapping reuse** | High | Every `sem_open` creates a NEW mmap even if already open. Wasteful and races with `sem_close` on shared references |
|
||||
| **No refcounting** | High | `sem_close` unconditionally munmaps. If two threads open the same semaphore, one close breaks the other |
|
||||
| **No cancellation safety** | Low | No `pthread_setcancelstate` around file ops |
|
||||
| **va_list not parsed** | Medium | `sem_open` hardcodes `value=0` when O_CREAT, ignoring mode and initial value from varargs |
|
||||
| **No `__sem_check_add_mapping` equivalent** | High | Opens named sem every time instead of reusing existing mapping |
|
||||
| **No O_NOFOLLOW** | Low | glibc uses `O_NOFOLLOW` for security |
|
||||
|
||||
---
|
||||
|
||||
## 4. Cross-Cutting Gaps
|
||||
|
||||
### Error Handling
|
||||
|
||||
| Area | glibc | relibc |
|
||||
|------|-------|--------|
|
||||
| errno thread-safety | TLS errno via kernel | `Cell<c_int>` per platform ✅ |
|
||||
| errno after close | Preserved (close may overwrite) | `let _ = Sys::close(fd)` — ignores errors ✅ |
|
||||
| EINTR | Handled in syscall wrappers | Not consistently handled ⚠️ |
|
||||
|
||||
### Signal Safety
|
||||
|
||||
| Function | glibc | relibc |
|
||||
|----------|-------|--------|
|
||||
| `sem_post` | AS-safe (futex) | AS-safe ✅ |
|
||||
| `sem_wait` | AS-safe (futex wait, EINTR) | No EINTR handling ⚠️ |
|
||||
| `eventfd` write/read | AS-safe | Not implemented |
|
||||
|
||||
### Thread Safety
|
||||
|
||||
| Area | glibc | relibc |
|
||||
|------|-------|--------|
|
||||
| sem_open refcount | Mutex-protected global list | None — race on same name ⚠️ |
|
||||
| sem_close/sem_unlink | Mutex-protected | None — concurrent close races ⚠️ |
|
||||
| signalfd mask | Per-process (kernel) | Per-call sigprocmask ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 5. Priority Improvement Plan
|
||||
|
||||
### Phase S1: Critical Correctness (1-2 weeks)
|
||||
|
||||
1. **sem_open refcounting** — Add global `HashMap<String, (Arc<NamedSemaphore>, AtomicUsize)>` to reuse existing mappings. `sem_close` decrements refcount, munmaps only when zero.
|
||||
2. **Eventfd implementation** — Implement `eventfd()` via `/scheme/event/eventfd/` using the existing scheme mechanism. Remove libwayland's inline copy.
|
||||
|
||||
### Phase S2: Completeness (2-3 weeks)
|
||||
|
||||
3. **signalfd read path** — Implement read(2) → `signalfd_siginfo` struct population. The `/scheme/event` fd must deliver signal info formatted as `signalfd_siginfo`.
|
||||
4. **sem_open va_list** — Parse `mode_t` and `value` from varargs when O_CREAT. Requires `crate::header::stdarg` or manual stack walking.
|
||||
5. **sem_open name canonicalization** — Prefix names with `/scheme/shm/sem.` for namespace isolation.
|
||||
|
||||
### Phase S3: Robustness (3-4 weeks)
|
||||
|
||||
6. **EINTR handling** — Wrap futex waits to retry on `EINTR`.
|
||||
7. **sem_open cancellation safety** — Add `pthread_setcancelstate` around file ops (if pthread cancellation is supported).
|
||||
8. **eventfd semaphore mode** — Implement `EFD_SEMAPHORE` counting semantics (decrements on read, blocks at 0).
|
||||
|
||||
### Phase S4: POSIX Conformance (2-3 weeks)
|
||||
|
||||
9. **eventfd_read/eventfd_write** — Convenience wrappers.
|
||||
10. **sem_open existing-semaphore reopening** — Handle `(oflag & O_CREAT) == 0` path (open existing without O_EXCL).
|
||||
11. **Signalfd signal delivery verification** — Runtime tests proving signals arrive via signalfd.
|
||||
|
||||
---
|
||||
|
||||
## 6. Eventfd Kernel Requirement
|
||||
|
||||
Unlike signalfd and sem_open which can be implemented in userspace via existing schemes (`/scheme/event`, `shm_open`), eventfd currently relies on libwayland's inline implementation that opens `/scheme/event/eventfd/`. A canonical relibc implementation should use the same path.
|
||||
|
||||
The `/scheme/event` kernel scheme needs:
|
||||
- Support for `eventfd` sub-path
|
||||
- EFD_SEMAPHORE counting semantics in kernel
|
||||
- Non-blocking reads returning `u64` count
|
||||
|
||||
This is a **kernel change** and should be tracked separately from relibc.
|
||||
|
||||
---
|
||||
|
||||
## 7. Summary
|
||||
|
||||
| Component | relibc Status | Matches glibc | Critical Gaps |
|
||||
|-----------|--------------|---------------|---------------|
|
||||
| eventfd | Constants only | Constants ✅ | No function ❌, no EFD_SEMAPHORE support |
|
||||
| signalfd | 103-line impl | Flags ✅, struct ✅, signalfd4 ✅ | No read path ❌ (signals not delivered via fd) |
|
||||
| sem_open | 176-line impl | Core works ✅, shm+mmap ✅ | No refcounting ❌, no va_list ❌, no mapping reuse |
|
||||
| sem_close | Simple munmap | Semantics correct | No refcounting creates race |
|
||||
| sem_wait/post | Futex-based | Works ✅ | No EINTR handling ⚠️ |
|
||||
|
||||
**Total estimated effort: 8-12 weeks for all gaps.**
|
||||
**Critical path: eventfd kernal + signalfd read + sem_open refcounting (Phase S1).**
|
||||
@@ -1,43 +1,115 @@
|
||||
diff --git a/daemon/src/lib.rs b/daemon/src/lib.rs
|
||||
index 9f507221..c57d91dc 100644
|
||||
--- a/daemon/src/lib.rs
|
||||
+++ b/daemon/src/lib.rs
|
||||
@@ -11,12 +11,23 @@ use redox_scheme::Socket;
|
||||
@@ -10,26 +10,35 @@ use libredox::Fd;
|
||||
use redox_scheme::scheme::{SchemeAsync, SchemeSync};
|
||||
use redox_scheme::Socket;
|
||||
|
||||
unsafe fn get_fd(var: &str) -> RawFd {
|
||||
- let fd: RawFd = std::env::var(var).unwrap().parse().unwrap();
|
||||
+ let fd: RawFd = match std::env::var(var)
|
||||
+ .map_err(|e| eprintln!("daemon: env var {var} not set: {e}"))
|
||||
+ .ok()
|
||||
+ .and_then(|val| {
|
||||
+ val.parse()
|
||||
+ .map_err(|e| eprintln!("daemon: failed to parse {var} as fd: {e}"))
|
||||
+ .ok()
|
||||
+ }) {
|
||||
+ Some(fd) => fd,
|
||||
+ None => return -1,
|
||||
-unsafe fn get_fd(var: &str) -> RawFd {
|
||||
- let fd: RawFd = match std::env::var(var)
|
||||
- .map_err(|e| eprintln!("daemon: env var {var} not set: {e}"))
|
||||
- .ok()
|
||||
- .and_then(|val| {
|
||||
- val.parse()
|
||||
- .map_err(|e| eprintln!("daemon: failed to parse {var} as fd: {e}"))
|
||||
- .ok()
|
||||
- }) {
|
||||
- Some(fd) => fd,
|
||||
- None => return -1,
|
||||
+unsafe fn get_fd(var: &str) -> Option<RawFd> {
|
||||
+ let value = match std::env::var(var) {
|
||||
+ Ok(value) => value,
|
||||
+ Err(_) => {
|
||||
+ let exe = std::env::args()
|
||||
+ .next()
|
||||
+ .unwrap_or_else(|| "daemon".to_string());
|
||||
+ eprintln!("daemon: {var} not set for {exe}; readiness notification disabled");
|
||||
+ return None;
|
||||
+ }
|
||||
+ };
|
||||
+ let fd: RawFd = match value.parse() {
|
||||
+ Ok(fd) => fd,
|
||||
+ Err(err) => {
|
||||
+ let exe = std::env::args()
|
||||
+ .next()
|
||||
+ .unwrap_or_else(|| "daemon".to_string());
|
||||
+ eprintln!("daemon: invalid {var} value {value:?} for {exe}: {err}; readiness notification disabled");
|
||||
+ return None;
|
||||
+ }
|
||||
};
|
||||
if unsafe { libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC) } == -1 {
|
||||
- panic!(
|
||||
+ eprintln!(
|
||||
eprintln!(
|
||||
"daemon: failed to set CLOEXEC flag for {var} fd: {}",
|
||||
io::Error::last_os_error()
|
||||
);
|
||||
+ return -1;
|
||||
- return -1;
|
||||
+ return None;
|
||||
}
|
||||
fd
|
||||
- fd
|
||||
+ Some(fd)
|
||||
}
|
||||
@@ -51,7 +62,11 @@ impl Daemon {
|
||||
|
||||
unsafe fn pass_fd(cmd: &mut Command, env: &str, fd: OwnedFd) {
|
||||
@@ -47,22 +56,20 @@ unsafe fn pass_fd(cmd: &mut Command, env: &str, fd: OwnedFd) {
|
||||
/// A long running background process that handles requests.
|
||||
#[must_use = "Daemon::ready must be called"]
|
||||
pub struct Daemon {
|
||||
- write_pipe: PipeWriter,
|
||||
+ write_pipe: Option<PipeWriter>,
|
||||
}
|
||||
|
||||
impl Daemon {
|
||||
/// Create a new daemon.
|
||||
pub fn new(f: impl FnOnce(Daemon) -> !) -> ! {
|
||||
- let write_pipe = unsafe { io::PipeWriter::from_raw_fd(get_fd("INIT_NOTIFY")) };
|
||||
+ let write_pipe = unsafe { get_fd("INIT_NOTIFY").map(io::PipeWriter::from_raw_fd) };
|
||||
|
||||
f(Daemon { write_pipe })
|
||||
}
|
||||
|
||||
/// Notify the process that the daemon is ready to accept requests.
|
||||
pub fn ready(mut self) {
|
||||
- self.write_pipe.write_all(&[0]).unwrap();
|
||||
+ if let Err(err) = self.write_pipe.write_all(&[0]) {
|
||||
+ if err.kind() != io::ErrorKind::BrokenPipe {
|
||||
+ eprintln!("daemon::ready write failed: {err}");
|
||||
+ }
|
||||
+ }
|
||||
- if let Err(err) = self.write_pipe.write_all(&[0]) {
|
||||
- if err.kind() != io::ErrorKind::BrokenPipe {
|
||||
- eprintln!("daemon::ready write failed: {err}");
|
||||
- }
|
||||
+ if let Some(write_pipe) = self.write_pipe.as_mut() {
|
||||
+ write_pipe.write_all(&[0]).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes `Command` as a child process.
|
||||
@@ -87,24 +94,26 @@ impl Daemon {
|
||||
/// A long running background process that handles requests using schemes.
|
||||
#[must_use = "SchemeDaemon::ready must be called"]
|
||||
pub struct SchemeDaemon {
|
||||
- write_pipe: PipeWriter,
|
||||
+ write_pipe: Option<PipeWriter>,
|
||||
}
|
||||
|
||||
impl SchemeDaemon {
|
||||
/// Create a new daemon for use with schemes.
|
||||
pub fn new(f: impl FnOnce(SchemeDaemon) -> !) -> ! {
|
||||
- let write_pipe = unsafe { io::PipeWriter::from_raw_fd(get_fd("INIT_NOTIFY")) };
|
||||
+ let write_pipe = unsafe { get_fd("INIT_NOTIFY").map(io::PipeWriter::from_raw_fd) };
|
||||
|
||||
f(SchemeDaemon { write_pipe })
|
||||
}
|
||||
|
||||
/// Notify the process that the scheme daemon is ready to accept requests.
|
||||
pub fn ready_with_fd(self, cap_fd: Fd) -> syscall::Result<()> {
|
||||
- syscall::call_wo(
|
||||
- self.write_pipe.as_raw_fd() as usize,
|
||||
- &cap_fd.into_raw().to_ne_bytes(),
|
||||
- syscall::CallFlags::FD,
|
||||
- &[],
|
||||
- )?;
|
||||
+ if let Some(write_pipe) = self.write_pipe {
|
||||
+ syscall::call_wo(
|
||||
+ write_pipe.as_raw_fd() as usize,
|
||||
+ &cap_fd.into_raw().to_ne_bytes(),
|
||||
+ syscall::CallFlags::FD,
|
||||
+ &[],
|
||||
+ )?;
|
||||
+ }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,26 +1,17 @@
|
||||
diff --git a/src/header/semaphore/cbindgen.toml b/src/header/semaphore/cbindgen.toml
|
||||
--- a/src/header/semaphore/cbindgen.toml
|
||||
+++ b/src/header/semaphore/cbindgen.toml
|
||||
@@ -3,6 +3,9 @@ include_guard = "_RELIBC_SEMAPHORE_H"
|
||||
after_includes = """
|
||||
#include <bits/timespec.h> // for timespec
|
||||
"""
|
||||
+trailer = """
|
||||
+#define SEM_FAILED ((sem_t *) -1)
|
||||
+"""
|
||||
language = "C"
|
||||
style = "Type"
|
||||
no_includes = true
|
||||
diff --git a/src/header/semaphore/mod.rs b/src/header/semaphore/mod.rs
|
||||
--- a/src/header/semaphore/mod.rs
|
||||
+++ b/src/header/semaphore/mod.rs
|
||||
@@ -2,12 +2,23 @@
|
||||
@@ -2,12 +2,27 @@
|
||||
//!
|
||||
//! See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/semaphore.h.html>.
|
||||
|
||||
+use alloc::collections::BTreeMap;
|
||||
+use alloc::vec::Vec;
|
||||
+use core::ptr;
|
||||
+use core::sync::atomic::{AtomicUsize, Ordering};
|
||||
+
|
||||
use crate::{
|
||||
+ c_str::CStr,
|
||||
header::{
|
||||
bits_timespec::timespec,
|
||||
+ errno::EINVAL,
|
||||
@@ -28,7 +19,6 @@ diff --git a/src/header/semaphore/mod.rs b/src/header/semaphore/mod.rs
|
||||
+ sys_mman::{
|
||||
+ MAP_FAILED, MAP_SHARED, PROT_READ, PROT_WRITE, mmap, munmap, shm_open, shm_unlink,
|
||||
+ },
|
||||
+ sys_stat::S_IRWXU,
|
||||
time::{CLOCK_MONOTONIC, CLOCK_REALTIME},
|
||||
},
|
||||
- platform::types::{c_char, c_int, c_long, c_uint, clockid_t},
|
||||
@@ -36,10 +26,11 @@ diff --git a/src/header/semaphore/mod.rs b/src/header/semaphore/mod.rs
|
||||
+ Pal, Sys, ERRNO,
|
||||
+ types::{c_char, c_int, c_long, c_uint, clockid_t, mode_t},
|
||||
+ },
|
||||
+ sync::Mutex,
|
||||
};
|
||||
|
||||
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/semaphore.h.html>.
|
||||
@@ -19,11 +30,57 @@ pub union sem_t {
|
||||
@@ -19,11 +34,86 @@
|
||||
pub align: c_long,
|
||||
}
|
||||
pub type RlctSempahore = crate::sync::Semaphore;
|
||||
@@ -50,6 +41,24 @@ diff --git a/src/header/semaphore/mod.rs b/src/header/semaphore/mod.rs
|
||||
+
|
||||
+const SEM_FAILED_PTR: *mut sem_t = usize::MAX as *mut sem_t;
|
||||
+
|
||||
+struct NamedSemEntry {
|
||||
+ ptr: *mut NamedSemaphore,
|
||||
+ size: usize,
|
||||
+ refs: AtomicUsize,
|
||||
+ unlinked: bool,
|
||||
+}
|
||||
+unsafe impl Send for NamedSemEntry {}
|
||||
+
|
||||
+static NAMED_SEMS: Mutex<Option<BTreeMap<Vec<u8>, NamedSemEntry>>> = Mutex::new(None);
|
||||
+
|
||||
+fn with_named_sems<R>(f: impl FnOnce(&mut BTreeMap<Vec<u8>, NamedSemEntry>) -> R) -> R {
|
||||
+ let mut guard = NAMED_SEMS.lock();
|
||||
+ if guard.is_none() {
|
||||
+ *guard = Some(BTreeMap::new());
|
||||
+ }
|
||||
+ f(guard.as_mut().unwrap())
|
||||
+}
|
||||
+
|
||||
+unsafe fn map_named_semaphore(
|
||||
+ name: *const c_char,
|
||||
+ oflag: c_int,
|
||||
@@ -91,14 +100,25 @@ diff --git a/src/header/semaphore/mod.rs b/src/header/semaphore/mod.rs
|
||||
+ return -1;
|
||||
+ }
|
||||
+ let named_ptr = sem as *mut NamedSemaphore;
|
||||
+ if unsafe { munmap(named_ptr as *mut crate::platform::types::c_void, core::mem::size_of::<NamedSemaphore>()) } != 0 {
|
||||
+ return -1;
|
||||
+ let mut should_unmap = false;
|
||||
+ with_named_sems(|map| {
|
||||
+ let key = map.iter().find(|(_, e)| e.ptr == named_ptr).map(|(k, _)| k.clone());
|
||||
+ if let Some(key) = key {
|
||||
+ let entry = map.get(&key).unwrap();
|
||||
+ let prev = entry.refs.fetch_sub(1, Ordering::Release);
|
||||
+ if prev == 1 { should_unmap = true; map.remove(&key); }
|
||||
+ } else { should_unmap = true; }
|
||||
+ });
|
||||
+ if should_unmap {
|
||||
+ if unsafe { munmap(named_ptr as *mut crate::platform::types::c_void, core::mem::size_of::<NamedSemaphore>()) } != 0 {
|
||||
+ return -1;
|
||||
+ }
|
||||
+ }
|
||||
+ 0
|
||||
}
|
||||
|
||||
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sem_destroy.html>.
|
||||
@@ -50,13 +107,13 @@ pub unsafe extern "C" fn sem_init(sem: *mut sem_t, _pshared: c_int, value: c_uin
|
||||
@@ -50,13 +140,31 @@
|
||||
}
|
||||
|
||||
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sem_open.html>.
|
||||
@@ -107,15 +127,34 @@ diff --git a/src/header/semaphore/mod.rs b/src/header/semaphore/mod.rs
|
||||
+#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn sem_open(
|
||||
name: *const c_char,
|
||||
oflag: c_int, /* (va_list) value: c_uint */
|
||||
- oflag: c_int, /* (va_list) value: c_uint */
|
||||
+ oflag: c_int, /* (va_list) mode: mode_t, value: c_uint */
|
||||
) -> *mut sem_t {
|
||||
- todo!("named semaphores")
|
||||
+ let value: c_uint = 0;
|
||||
+ unsafe { map_named_semaphore(name, oflag, S_IRWXU as mode_t, value) }
|
||||
+ if name.is_null() { ERRNO.set(EINVAL); return SEM_FAILED_PTR; }
|
||||
+ let name_bytes = unsafe { CStr::from_ptr(name) }.to_bytes().to_vec();
|
||||
+ let create = (oflag & O_CREAT) != 0;
|
||||
+ let excl = (oflag & O_EXCL) != 0;
|
||||
+ let existing = with_named_sems(|map| map.get(&name_bytes).map(|e| e.ptr));
|
||||
+ if let Some(ptr) = existing {
|
||||
+ if excl { ERRNO.set(crate::header::errno::EEXIST); return SEM_FAILED_PTR; }
|
||||
+ with_named_sems(|map| { if let Some(e) = map.get(&name_bytes) { e.refs.fetch_add(1, Ordering::Relaxed); } });
|
||||
+ return ptr as *mut NamedSemaphore as *mut sem_t;
|
||||
+ }
|
||||
+ let (mode, value): (mode_t, c_uint) = {
|
||||
+ let oflag_ptr: *const c_int = &oflag;
|
||||
+ let mode_ptr = unsafe { oflag_ptr.add(1) as *const mode_t };
|
||||
+ let value_ptr = unsafe { oflag_ptr.add(2) as *const c_uint };
|
||||
+ (unsafe { *mode_ptr }, if create { unsafe { *value_ptr } } else { 0 })
|
||||
+ };
|
||||
+ let ptr = unsafe { map_named_semaphore(name, oflag, mode, value) };
|
||||
+ if ptr == SEM_FAILED_PTR { return SEM_FAILED_PTR; }
|
||||
+ with_named_sems(|map| { map.insert(name_bytes, NamedSemEntry { ptr: ptr as *mut NamedSemaphore, size: core::mem::size_of::<NamedSemaphore>(), refs: AtomicUsize::new(1), unlinked: false }); });
|
||||
+ ptr
|
||||
}
|
||||
|
||||
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sem_post.html>.
|
||||
@@ -76,9 +133,13 @@ pub unsafe extern "C" fn sem_trywait(sem: *mut sem_t) -> c_int {
|
||||
@@ -76,9 +184,12 @@
|
||||
}
|
||||
|
||||
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sem_unlink.html>.
|
||||
@@ -123,12 +162,26 @@ diff --git a/src/header/semaphore/mod.rs b/src/header/semaphore/mod.rs
|
||||
+#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn sem_unlink(name: *const c_char) -> c_int {
|
||||
- todo!("named semaphores")
|
||||
+ if name.is_null() {
|
||||
+ ERRNO.set(EINVAL);
|
||||
+ return -1;
|
||||
+ }
|
||||
+ if name.is_null() { ERRNO.set(EINVAL); return -1; }
|
||||
+ let name_bytes = unsafe { CStr::from_ptr(name) }.to_bytes().to_vec();
|
||||
+ with_named_sems(|map| { if let Some(e) = map.get_mut(&name_bytes) { e.unlinked = true; } });
|
||||
+ unsafe { shm_unlink(name) }
|
||||
}
|
||||
|
||||
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sem_trywait.html>.
|
||||
|
||||
|
||||
diff --git a/src/header/semaphore/cbindgen.toml b/src/header/semaphore/cbindgen.toml
|
||||
--- a/src/header/semaphore/cbindgen.toml
|
||||
+++ b/src/header/semaphore/cbindgen.toml
|
||||
@@ -3,6 +3,9 @@
|
||||
after_includes = """
|
||||
#include <bits/timespec.h> // for timespec
|
||||
"""
|
||||
+trailer = """
|
||||
+#define SEM_FAILED ((sem_t *) -1)
|
||||
+"""
|
||||
language = "C"
|
||||
style = "Type"
|
||||
no_includes = true
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
mod scheme;
|
||||
|
||||
use std::io::Write;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use scheme::AccessibilityScheme;
|
||||
use std::io::Write;
|
||||
|
||||
fn log_msg(level: &str, msg: &str) {
|
||||
let _ = writeln!(std::io::stderr(), "[accessibility] {} {}", level, msg);
|
||||
@@ -13,7 +10,7 @@ fn log_msg(level: &str, msg: &str) {
|
||||
fn main() {
|
||||
let mut scheme = AccessibilityScheme::new();
|
||||
|
||||
let socket = redox_scheme::Socket::nonblock("accessibility")
|
||||
let socket = redox_scheme::Socket::create("accessibility")
|
||||
.expect("accessibility: failed to register scheme:accessibility");
|
||||
log_msg("INFO", "registered scheme:accessibility");
|
||||
|
||||
@@ -25,15 +22,16 @@ fn main() {
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
log_msg("WARN", &format!("scheme read error (ignoring): {}", e));
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
continue;
|
||||
log_msg("ERROR", &format!("scheme read error: {}", e));
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
match request.handle_scheme_block_mut(&mut scheme) {
|
||||
Ok(response) => {
|
||||
if let Err(e) = socket.write_response(response, redox_scheme::SignalBehavior::Restart) {
|
||||
if let Err(e) =
|
||||
socket.write_response(response, redox_scheme::SignalBehavior::Restart)
|
||||
{
|
||||
log_msg("ERROR", &format!("failed to write response: {}", e));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use syscall::error::{Error, Result, EBADF, EINVAL, ENOENT};
|
||||
use syscall::flag::{MODE_DIR, MODE_FILE};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
enum StickyKeyState {
|
||||
pub(crate) enum StickyKeyState {
|
||||
Off,
|
||||
Latched,
|
||||
Locked,
|
||||
@@ -251,6 +251,29 @@ impl redox_scheme::SchemeBlockMut for AccessibilityScheme {
|
||||
}
|
||||
Ok(Some(buf.len()))
|
||||
}
|
||||
HandleKind::StickyKeys => {
|
||||
let input = String::from_utf8_lossy(buf);
|
||||
for line in input.lines() {
|
||||
let mut fields = line.split_whitespace();
|
||||
let Some("key") = fields.next() else {
|
||||
continue;
|
||||
};
|
||||
let Some(code) = fields.next().and_then(|field| field.parse::<u16>().ok())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(pressed) = fields.next().and_then(|field| field.parse::<bool>().ok())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let now_ms = fields
|
||||
.next()
|
||||
.and_then(|field| field.parse::<u64>().ok())
|
||||
.unwrap_or(0);
|
||||
let _ = self.filter_key(code, pressed, now_ms);
|
||||
}
|
||||
Ok(Some(buf.len()))
|
||||
}
|
||||
_ => Err(Error::new(EINVAL)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
mod scheme;
|
||||
|
||||
use std::io::Write;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use scheme::ImeScheme;
|
||||
use std::io::Write;
|
||||
|
||||
fn log_msg(level: &str, msg: &str) {
|
||||
let _ = writeln!(std::io::stderr(), "[ime] {} {}", level, msg);
|
||||
@@ -13,8 +10,7 @@ fn log_msg(level: &str, msg: &str) {
|
||||
fn main() {
|
||||
let mut scheme = ImeScheme::new();
|
||||
|
||||
let socket = redox_scheme::Socket::nonblock("ime")
|
||||
.expect("ime: failed to register scheme:ime");
|
||||
let socket = redox_scheme::Socket::create("ime").expect("ime: failed to register scheme:ime");
|
||||
log_msg("INFO", "registered scheme:ime");
|
||||
|
||||
loop {
|
||||
@@ -25,15 +21,16 @@ fn main() {
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
log_msg("WARN", &format!("scheme read error (ignoring): {}", e));
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
continue;
|
||||
log_msg("ERROR", &format!("scheme read error: {}", e));
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
match request.handle_scheme_block_mut(&mut scheme) {
|
||||
Ok(response) => {
|
||||
if let Err(e) = socket.write_response(response, redox_scheme::SignalBehavior::Restart) {
|
||||
if let Err(e) =
|
||||
socket.write_response(response, redox_scheme::SignalBehavior::Restart)
|
||||
{
|
||||
log_msg("ERROR", &format!("failed to write response: {}", e));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,17 +93,50 @@ impl BasicLatinEngine {
|
||||
}
|
||||
fn scancode_to_char(scancode: u8, shift: bool) -> Option<char> {
|
||||
let lower = match scancode {
|
||||
0x02 => '1', 0x03 => '2', 0x04 => '3', 0x05 => '4', 0x06 => '5',
|
||||
0x07 => '6', 0x08 => '7', 0x09 => '8', 0x0A => '9', 0x0B => '0',
|
||||
0x10 => 'q', 0x11 => 'w', 0x12 => 'e', 0x13 => 'r', 0x14 => 't',
|
||||
0x15 => 'y', 0x16 => 'u', 0x17 => 'i', 0x18 => 'o', 0x19 => 'p',
|
||||
0x1E => 'a', 0x1F => 's', 0x20 => 'd', 0x21 => 'f', 0x22 => 'g',
|
||||
0x23 => 'h', 0x24 => 'j', 0x25 => 'k', 0x26 => 'l',
|
||||
0x2C => 'z', 0x2D => 'x', 0x2E => 'c', 0x2F => 'v', 0x30 => 'b',
|
||||
0x31 => 'n', 0x32 => 'm',
|
||||
0x27 => ';', 0x28 => '\'', 0x29 => '`',
|
||||
0x33 => ',', 0x34 => '.', 0x35 => '/',
|
||||
0x0C => '-', 0x0D => '=',
|
||||
0x02 => '1',
|
||||
0x03 => '2',
|
||||
0x04 => '3',
|
||||
0x05 => '4',
|
||||
0x06 => '5',
|
||||
0x07 => '6',
|
||||
0x08 => '7',
|
||||
0x09 => '8',
|
||||
0x0A => '9',
|
||||
0x0B => '0',
|
||||
0x10 => 'q',
|
||||
0x11 => 'w',
|
||||
0x12 => 'e',
|
||||
0x13 => 'r',
|
||||
0x14 => 't',
|
||||
0x15 => 'y',
|
||||
0x16 => 'u',
|
||||
0x17 => 'i',
|
||||
0x18 => 'o',
|
||||
0x19 => 'p',
|
||||
0x1E => 'a',
|
||||
0x1F => 's',
|
||||
0x20 => 'd',
|
||||
0x21 => 'f',
|
||||
0x22 => 'g',
|
||||
0x23 => 'h',
|
||||
0x24 => 'j',
|
||||
0x25 => 'k',
|
||||
0x26 => 'l',
|
||||
0x2C => 'z',
|
||||
0x2D => 'x',
|
||||
0x2E => 'c',
|
||||
0x2F => 'v',
|
||||
0x30 => 'b',
|
||||
0x31 => 'n',
|
||||
0x32 => 'm',
|
||||
0x27 => ';',
|
||||
0x28 => '\'',
|
||||
0x29 => '`',
|
||||
0x33 => ',',
|
||||
0x34 => '.',
|
||||
0x35 => '/',
|
||||
0x0C => '-',
|
||||
0x0D => '=',
|
||||
_ => return None,
|
||||
};
|
||||
if shift {
|
||||
@@ -157,8 +190,10 @@ impl ImeEngine for BasicLatinEngine {
|
||||
let is_dead_trigger = scancode == self.compose_scancode;
|
||||
if is_dead_trigger {
|
||||
let dead_keys = [
|
||||
('\u{0300}', "grave"), ('\u{0301}', "acute"),
|
||||
('\u{0302}', "circumflex"), ('\u{0308}', "diaeresis"),
|
||||
('\u{0300}', "grave"),
|
||||
('\u{0301}', "acute"),
|
||||
('\u{0302}', "circumflex"),
|
||||
('\u{0308}', "diaeresis"),
|
||||
('\u{030C}', "caron"),
|
||||
];
|
||||
let (dk, _name) = dead_keys[self.dead_key_index % dead_keys.len()];
|
||||
@@ -206,6 +241,9 @@ impl ImeScheme {
|
||||
self.state.composing = false;
|
||||
self.state.preedit.clear();
|
||||
}
|
||||
if result.candidates_changed {
|
||||
self.state.composing = !self.engine.candidates().is_empty();
|
||||
}
|
||||
if !result.preedit.is_empty() {
|
||||
self.state.composing = true;
|
||||
self.state.preedit = result.preedit;
|
||||
@@ -235,13 +273,11 @@ impl redox_scheme::SchemeBlockMut for ImeScheme {
|
||||
|
||||
let content: Vec<u8> = match &handle.kind {
|
||||
HandleKind::Root => "state\ncompose\ncandidates\n".as_bytes().to_vec(),
|
||||
HandleKind::State => {
|
||||
format!(
|
||||
"composing={}\npreedit={}\ncommitted={}\n",
|
||||
self.state.composing, self.state.preedit, self.state.committed
|
||||
)
|
||||
.into_bytes()
|
||||
}
|
||||
HandleKind::State => format!(
|
||||
"composing={}\npreedit={}\ncommitted={}\n",
|
||||
self.state.composing, self.state.preedit, self.state.committed
|
||||
)
|
||||
.into_bytes(),
|
||||
HandleKind::Compose => "basic-latin\n".as_bytes().to_vec(),
|
||||
HandleKind::Candidates => {
|
||||
let mut out = String::new();
|
||||
@@ -267,9 +303,34 @@ impl redox_scheme::SchemeBlockMut for ImeScheme {
|
||||
match &handle.kind {
|
||||
HandleKind::Compose => {
|
||||
let input = String::from_utf8_lossy(buf);
|
||||
if input.trim() == "reset" {
|
||||
self.engine.reset();
|
||||
self.state = InputState::default();
|
||||
for line in input.lines() {
|
||||
let line = line.trim();
|
||||
if line == "reset" {
|
||||
self.engine.reset();
|
||||
self.state = InputState::default();
|
||||
continue;
|
||||
}
|
||||
let mut fields = line.split_whitespace();
|
||||
let Some("feed") = fields.next() else {
|
||||
continue;
|
||||
};
|
||||
let Some(scancode) = fields.next().and_then(|field| field.parse::<u8>().ok())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(pressed) = fields.next().and_then(|field| field.parse::<bool>().ok())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let shift = fields
|
||||
.next()
|
||||
.and_then(|field| field.parse::<bool>().ok())
|
||||
.unwrap_or(false);
|
||||
let altgr = fields
|
||||
.next()
|
||||
.and_then(|field| field.parse::<bool>().ok())
|
||||
.unwrap_or(false);
|
||||
self.feed(scancode, pressed, shift, altgr);
|
||||
}
|
||||
Ok(Some(buf.len()))
|
||||
}
|
||||
|
||||
@@ -79,8 +79,8 @@ impl Keymap {
|
||||
dead_keys: Vec<DeadKeyEntry>,
|
||||
}
|
||||
|
||||
let file: KeymapFile = serde_json::from_str(json_str)
|
||||
.map_err(|e| format!("parse error: {}", e))?;
|
||||
let file: KeymapFile =
|
||||
serde_json::from_str(json_str).map_err(|e| format!("parse error: {}", e))?;
|
||||
|
||||
let mut map = HashMap::new();
|
||||
for entry in file.entries {
|
||||
@@ -119,192 +119,609 @@ impl BuiltinKeymaps {
|
||||
|
||||
fn common_dead_keys() -> Vec<DeadKeyEntry> {
|
||||
vec![
|
||||
DeadKeyEntry { dead_key: '`', base: 'a', composed: 'à' },
|
||||
DeadKeyEntry { dead_key: '`', base: 'e', composed: 'è' },
|
||||
DeadKeyEntry { dead_key: '`', base: 'i', composed: 'ì' },
|
||||
DeadKeyEntry { dead_key: '`', base: 'o', composed: 'ò' },
|
||||
DeadKeyEntry { dead_key: '`', base: 'u', composed: 'ù' },
|
||||
DeadKeyEntry { dead_key: '`', base: 'A', composed: 'À' },
|
||||
DeadKeyEntry { dead_key: '`', base: 'E', composed: 'È' },
|
||||
DeadKeyEntry { dead_key: '`', base: 'I', composed: 'Ì' },
|
||||
DeadKeyEntry { dead_key: '`', base: 'O', composed: 'Ò' },
|
||||
DeadKeyEntry { dead_key: '`', base: 'U', composed: 'Ù' },
|
||||
DeadKeyEntry { dead_key: '\'', base: 'a', composed: 'á' },
|
||||
DeadKeyEntry { dead_key: '\'', base: 'e', composed: 'é' },
|
||||
DeadKeyEntry { dead_key: '\'', base: 'i', composed: 'í' },
|
||||
DeadKeyEntry { dead_key: '\'', base: 'o', composed: 'ó' },
|
||||
DeadKeyEntry { dead_key: '\'', base: 'u', composed: 'ú' },
|
||||
DeadKeyEntry { dead_key: '\'', base: 'A', composed: 'Á' },
|
||||
DeadKeyEntry { dead_key: '\'', base: 'E', composed: 'É' },
|
||||
DeadKeyEntry { dead_key: '\'', base: 'I', composed: 'Í' },
|
||||
DeadKeyEntry { dead_key: '\'', base: 'O', composed: 'Ó' },
|
||||
DeadKeyEntry { dead_key: '\'', base: 'U', composed: 'Ú' },
|
||||
DeadKeyEntry { dead_key: '^', base: 'a', composed: 'â' },
|
||||
DeadKeyEntry { dead_key: '^', base: 'e', composed: 'ê' },
|
||||
DeadKeyEntry { dead_key: '^', base: 'i', composed: 'î' },
|
||||
DeadKeyEntry { dead_key: '^', base: 'o', composed: 'ô' },
|
||||
DeadKeyEntry { dead_key: '^', base: 'u', composed: 'û' },
|
||||
DeadKeyEntry { dead_key: '^', base: 'A', composed: 'Â' },
|
||||
DeadKeyEntry { dead_key: '^', base: 'E', composed: 'Ê' },
|
||||
DeadKeyEntry { dead_key: '^', base: 'I', composed: 'Î' },
|
||||
DeadKeyEntry { dead_key: '^', base: 'O', composed: 'Ô' },
|
||||
DeadKeyEntry { dead_key: '^', base: 'U', composed: 'Û' },
|
||||
DeadKeyEntry { dead_key: '"', base: 'a', composed: 'ä' },
|
||||
DeadKeyEntry { dead_key: '"', base: 'e', composed: 'ë' },
|
||||
DeadKeyEntry { dead_key: '"', base: 'i', composed: 'ï' },
|
||||
DeadKeyEntry { dead_key: '"', base: 'o', composed: 'ö' },
|
||||
DeadKeyEntry { dead_key: '"', base: 'u', composed: 'ü' },
|
||||
DeadKeyEntry { dead_key: '"', base: 'A', composed: 'Ä' },
|
||||
DeadKeyEntry { dead_key: '"', base: 'E', composed: 'Ë' },
|
||||
DeadKeyEntry { dead_key: '"', base: 'I', composed: 'Ï' },
|
||||
DeadKeyEntry { dead_key: '"', base: 'O', composed: 'Ö' },
|
||||
DeadKeyEntry { dead_key: '"', base: 'U', composed: 'Ü' },
|
||||
DeadKeyEntry { dead_key: '~', base: 'a', composed: 'ã' },
|
||||
DeadKeyEntry { dead_key: '~', base: 'n', composed: 'ñ' },
|
||||
DeadKeyEntry { dead_key: '~', base: 'o', composed: 'õ' },
|
||||
DeadKeyEntry { dead_key: '~', base: 'A', composed: 'Ã' },
|
||||
DeadKeyEntry { dead_key: '~', base: 'N', composed: 'Ñ' },
|
||||
DeadKeyEntry { dead_key: '~', base: 'O', composed: 'Õ' },
|
||||
DeadKeyEntry {
|
||||
dead_key: '`',
|
||||
base: 'a',
|
||||
composed: 'à',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '`',
|
||||
base: 'e',
|
||||
composed: 'è',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '`',
|
||||
base: 'i',
|
||||
composed: 'ì',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '`',
|
||||
base: 'o',
|
||||
composed: 'ò',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '`',
|
||||
base: 'u',
|
||||
composed: 'ù',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '`',
|
||||
base: 'A',
|
||||
composed: 'À',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '`',
|
||||
base: 'E',
|
||||
composed: 'È',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '`',
|
||||
base: 'I',
|
||||
composed: 'Ì',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '`',
|
||||
base: 'O',
|
||||
composed: 'Ò',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '`',
|
||||
base: 'U',
|
||||
composed: 'Ù',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '\'',
|
||||
base: 'a',
|
||||
composed: 'á',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '\'',
|
||||
base: 'e',
|
||||
composed: 'é',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '\'',
|
||||
base: 'i',
|
||||
composed: 'í',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '\'',
|
||||
base: 'o',
|
||||
composed: 'ó',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '\'',
|
||||
base: 'u',
|
||||
composed: 'ú',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '\'',
|
||||
base: 'A',
|
||||
composed: 'Á',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '\'',
|
||||
base: 'E',
|
||||
composed: 'É',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '\'',
|
||||
base: 'I',
|
||||
composed: 'Í',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '\'',
|
||||
base: 'O',
|
||||
composed: 'Ó',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '\'',
|
||||
base: 'U',
|
||||
composed: 'Ú',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '^',
|
||||
base: 'a',
|
||||
composed: 'â',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '^',
|
||||
base: 'e',
|
||||
composed: 'ê',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '^',
|
||||
base: 'i',
|
||||
composed: 'î',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '^',
|
||||
base: 'o',
|
||||
composed: 'ô',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '^',
|
||||
base: 'u',
|
||||
composed: 'û',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '^',
|
||||
base: 'A',
|
||||
composed: 'Â',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '^',
|
||||
base: 'E',
|
||||
composed: 'Ê',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '^',
|
||||
base: 'I',
|
||||
composed: 'Î',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '^',
|
||||
base: 'O',
|
||||
composed: 'Ô',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '^',
|
||||
base: 'U',
|
||||
composed: 'Û',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '"',
|
||||
base: 'a',
|
||||
composed: 'ä',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '"',
|
||||
base: 'e',
|
||||
composed: 'ë',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '"',
|
||||
base: 'i',
|
||||
composed: 'ï',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '"',
|
||||
base: 'o',
|
||||
composed: 'ö',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '"',
|
||||
base: 'u',
|
||||
composed: 'ü',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '"',
|
||||
base: 'A',
|
||||
composed: 'Ä',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '"',
|
||||
base: 'E',
|
||||
composed: 'Ë',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '"',
|
||||
base: 'I',
|
||||
composed: 'Ï',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '"',
|
||||
base: 'O',
|
||||
composed: 'Ö',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '"',
|
||||
base: 'U',
|
||||
composed: 'Ü',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '~',
|
||||
base: 'a',
|
||||
composed: 'ã',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '~',
|
||||
base: 'n',
|
||||
composed: 'ñ',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '~',
|
||||
base: 'o',
|
||||
composed: 'õ',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '~',
|
||||
base: 'A',
|
||||
composed: 'Ã',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '~',
|
||||
base: 'N',
|
||||
composed: 'Ñ',
|
||||
},
|
||||
DeadKeyEntry {
|
||||
dead_key: '~',
|
||||
base: 'O',
|
||||
composed: 'Õ',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn make_us() -> Keymap {
|
||||
let mut entries = HashMap::new();
|
||||
let bindings: &[(u8, char, char)] = &[
|
||||
(0x01, '\x1B', '\x1B'), (0x02, '1', '!'), (0x03, '2', '@'),
|
||||
(0x04, '3', '#'), (0x05, '4', '$'), (0x06, '5', '%'),
|
||||
(0x07, '6', '^'), (0x08, '7', '&'), (0x09, '8', '*'),
|
||||
(0x0A, '9', '('), (0x0B, '0', ')'), (0x0C, '-', '_'),
|
||||
(0x0D, '=', '+'), (0x0E, '\x7F', '\x7F'), (0x0F, '\t', '\t'),
|
||||
(0x10, 'q', 'Q'), (0x11, 'w', 'W'), (0x12, 'e', 'E'),
|
||||
(0x13, 'r', 'R'), (0x14, 't', 'T'), (0x15, 'y', 'Y'),
|
||||
(0x16, 'u', 'U'), (0x17, 'i', 'I'), (0x18, 'o', 'O'),
|
||||
(0x19, 'p', 'P'), (0x1A, '[', '{'), (0x1B, ']', '}'),
|
||||
(0x1C, '\n', '\n'), (0x1E, 'a', 'A'), (0x1F, 's', 'S'),
|
||||
(0x20, 'd', 'D'), (0x21, 'f', 'F'), (0x22, 'g', 'G'),
|
||||
(0x23, 'h', 'H'), (0x24, 'j', 'J'), (0x25, 'k', 'K'),
|
||||
(0x26, 'l', 'L'), (0x27, ';', ':'), (0x28, '\'', '"'),
|
||||
(0x29, '`', '~'), (0x2B, '\\', '|'), (0x2C, 'z', 'Z'),
|
||||
(0x2D, 'x', 'X'), (0x2E, 'c', 'C'), (0x2F, 'v', 'V'),
|
||||
(0x30, 'b', 'B'), (0x31, 'n', 'N'), (0x32, 'm', 'M'),
|
||||
(0x33, ',', '<'), (0x34, '.', '>'), (0x35, '/', '?'),
|
||||
(0x01, '\x1B', '\x1B'),
|
||||
(0x02, '1', '!'),
|
||||
(0x03, '2', '@'),
|
||||
(0x04, '3', '#'),
|
||||
(0x05, '4', '$'),
|
||||
(0x06, '5', '%'),
|
||||
(0x07, '6', '^'),
|
||||
(0x08, '7', '&'),
|
||||
(0x09, '8', '*'),
|
||||
(0x0A, '9', '('),
|
||||
(0x0B, '0', ')'),
|
||||
(0x0C, '-', '_'),
|
||||
(0x0D, '=', '+'),
|
||||
(0x0E, '\x7F', '\x7F'),
|
||||
(0x0F, '\t', '\t'),
|
||||
(0x10, 'q', 'Q'),
|
||||
(0x11, 'w', 'W'),
|
||||
(0x12, 'e', 'E'),
|
||||
(0x13, 'r', 'R'),
|
||||
(0x14, 't', 'T'),
|
||||
(0x15, 'y', 'Y'),
|
||||
(0x16, 'u', 'U'),
|
||||
(0x17, 'i', 'I'),
|
||||
(0x18, 'o', 'O'),
|
||||
(0x19, 'p', 'P'),
|
||||
(0x1A, '[', '{'),
|
||||
(0x1B, ']', '}'),
|
||||
(0x1C, '\n', '\n'),
|
||||
(0x1E, 'a', 'A'),
|
||||
(0x1F, 's', 'S'),
|
||||
(0x20, 'd', 'D'),
|
||||
(0x21, 'f', 'F'),
|
||||
(0x22, 'g', 'G'),
|
||||
(0x23, 'h', 'H'),
|
||||
(0x24, 'j', 'J'),
|
||||
(0x25, 'k', 'K'),
|
||||
(0x26, 'l', 'L'),
|
||||
(0x27, ';', ':'),
|
||||
(0x28, '\'', '"'),
|
||||
(0x29, '`', '~'),
|
||||
(0x2B, '\\', '|'),
|
||||
(0x2C, 'z', 'Z'),
|
||||
(0x2D, 'x', 'X'),
|
||||
(0x2E, 'c', 'C'),
|
||||
(0x2F, 'v', 'V'),
|
||||
(0x30, 'b', 'B'),
|
||||
(0x31, 'n', 'N'),
|
||||
(0x32, 'm', 'M'),
|
||||
(0x33, ',', '<'),
|
||||
(0x34, '.', '>'),
|
||||
(0x35, '/', '?'),
|
||||
(0x39, ' ', ' '),
|
||||
];
|
||||
for &(sc, normal, shifted) in bindings {
|
||||
entries.insert(sc, KeymapEntry { scancode: sc, normal, shifted, altgr: '\0', altgr_shifted: '\0' });
|
||||
entries.insert(
|
||||
sc,
|
||||
KeymapEntry {
|
||||
scancode: sc,
|
||||
normal,
|
||||
shifted,
|
||||
altgr: '\0',
|
||||
altgr_shifted: '\0',
|
||||
},
|
||||
);
|
||||
}
|
||||
let dead_keys = Self::common_dead_keys();
|
||||
Keymap { name: "us".into(), entries, compose: Vec::new(), dead_keys }
|
||||
Keymap {
|
||||
name: "us".into(),
|
||||
entries,
|
||||
compose: Vec::new(),
|
||||
dead_keys,
|
||||
}
|
||||
}
|
||||
|
||||
fn make_gb() -> Keymap {
|
||||
let mut km = Self::make_us();
|
||||
km.name = "gb".into();
|
||||
if let Some(e) = km.entries.get_mut(&0x29) { e.shifted = '¬'; }
|
||||
if let Some(e) = km.entries.get_mut(&0x2B) { e.normal = '#'; e.shifted = '~'; }
|
||||
if let Some(e) = km.entries.get_mut(&0x29) {
|
||||
e.shifted = '¬';
|
||||
}
|
||||
if let Some(e) = km.entries.get_mut(&0x2B) {
|
||||
e.normal = '#';
|
||||
e.shifted = '~';
|
||||
}
|
||||
km
|
||||
}
|
||||
|
||||
fn make_dvorak() -> Keymap {
|
||||
let mut entries = HashMap::new();
|
||||
let remap: &[(u8, char, char)] = &[
|
||||
(0x01, '\x1B', '\x1B'), (0x02, '1', '!'), (0x03, '2', '@'),
|
||||
(0x04, '3', '#'), (0x05, '4', '$'), (0x06, '5', '%'),
|
||||
(0x07, '6', '^'), (0x08, '7', '&'), (0x09, '8', '*'),
|
||||
(0x0A, '9', '('), (0x0B, '0', ')'), (0x0C, '[', '{'),
|
||||
(0x0D, ']', '}'), (0x0E, '\x7F', '\x7F'), (0x0F, '\t', '\t'),
|
||||
(0x10, '\'', '"'), (0x11, ',', '<'), (0x12, '.', '>'),
|
||||
(0x13, 'p', 'P'), (0x14, 'y', 'Y'), (0x15, 'f', 'F'),
|
||||
(0x16, 'g', 'G'), (0x17, 'c', 'C'), (0x18, 'r', 'R'),
|
||||
(0x19, 'l', 'L'), (0x1A, '/', '?'), (0x1B, '=', '+'),
|
||||
(0x1C, '\n', '\n'), (0x1E, 'a', 'A'), (0x1F, 'o', 'O'),
|
||||
(0x20, 'e', 'E'), (0x21, 'u', 'U'), (0x22, 'i', 'I'),
|
||||
(0x23, 'd', 'D'), (0x24, 'h', 'H'), (0x25, 't', 'T'),
|
||||
(0x26, 'n', 'N'), (0x27, 's', 'S'), (0x28, '-', '_'),
|
||||
(0x29, '`', '~'), (0x2B, '\\', '|'), (0x2C, ';', ':'),
|
||||
(0x2D, 'q', 'Q'), (0x2E, 'j', 'J'), (0x2F, 'k', 'K'),
|
||||
(0x30, 'x', 'X'), (0x31, 'b', 'B'), (0x32, 'm', 'M'),
|
||||
(0x33, 'w', 'W'), (0x34, 'v', 'V'), (0x35, 'z', 'Z'),
|
||||
(0x01, '\x1B', '\x1B'),
|
||||
(0x02, '1', '!'),
|
||||
(0x03, '2', '@'),
|
||||
(0x04, '3', '#'),
|
||||
(0x05, '4', '$'),
|
||||
(0x06, '5', '%'),
|
||||
(0x07, '6', '^'),
|
||||
(0x08, '7', '&'),
|
||||
(0x09, '8', '*'),
|
||||
(0x0A, '9', '('),
|
||||
(0x0B, '0', ')'),
|
||||
(0x0C, '[', '{'),
|
||||
(0x0D, ']', '}'),
|
||||
(0x0E, '\x7F', '\x7F'),
|
||||
(0x0F, '\t', '\t'),
|
||||
(0x10, '\'', '"'),
|
||||
(0x11, ',', '<'),
|
||||
(0x12, '.', '>'),
|
||||
(0x13, 'p', 'P'),
|
||||
(0x14, 'y', 'Y'),
|
||||
(0x15, 'f', 'F'),
|
||||
(0x16, 'g', 'G'),
|
||||
(0x17, 'c', 'C'),
|
||||
(0x18, 'r', 'R'),
|
||||
(0x19, 'l', 'L'),
|
||||
(0x1A, '/', '?'),
|
||||
(0x1B, '=', '+'),
|
||||
(0x1C, '\n', '\n'),
|
||||
(0x1E, 'a', 'A'),
|
||||
(0x1F, 'o', 'O'),
|
||||
(0x20, 'e', 'E'),
|
||||
(0x21, 'u', 'U'),
|
||||
(0x22, 'i', 'I'),
|
||||
(0x23, 'd', 'D'),
|
||||
(0x24, 'h', 'H'),
|
||||
(0x25, 't', 'T'),
|
||||
(0x26, 'n', 'N'),
|
||||
(0x27, 's', 'S'),
|
||||
(0x28, '-', '_'),
|
||||
(0x29, '`', '~'),
|
||||
(0x2B, '\\', '|'),
|
||||
(0x2C, ';', ':'),
|
||||
(0x2D, 'q', 'Q'),
|
||||
(0x2E, 'j', 'J'),
|
||||
(0x2F, 'k', 'K'),
|
||||
(0x30, 'x', 'X'),
|
||||
(0x31, 'b', 'B'),
|
||||
(0x32, 'm', 'M'),
|
||||
(0x33, 'w', 'W'),
|
||||
(0x34, 'v', 'V'),
|
||||
(0x35, 'z', 'Z'),
|
||||
(0x39, ' ', ' '),
|
||||
];
|
||||
for &(sc, normal, shifted) in remap {
|
||||
entries.insert(sc, KeymapEntry { scancode: sc, normal, shifted, altgr: '\0', altgr_shifted: '\0' });
|
||||
entries.insert(
|
||||
sc,
|
||||
KeymapEntry {
|
||||
scancode: sc,
|
||||
normal,
|
||||
shifted,
|
||||
altgr: '\0',
|
||||
altgr_shifted: '\0',
|
||||
},
|
||||
);
|
||||
}
|
||||
Keymap {
|
||||
name: "dvorak".into(),
|
||||
entries,
|
||||
compose: Vec::new(),
|
||||
dead_keys: Self::common_dead_keys(),
|
||||
}
|
||||
Keymap { name: "dvorak".into(), entries, compose: Vec::new(), dead_keys: Self::common_dead_keys() }
|
||||
}
|
||||
|
||||
fn make_azerty() -> Keymap {
|
||||
let mut entries = HashMap::new();
|
||||
let bindings: &[(u8, char, char)] = &[
|
||||
(0x01, '\x1B', '\x1B'), (0x02, '&', '1'), (0x03, 'é', '2'),
|
||||
(0x04, '"', '3'), (0x05, '\'', '4'), (0x06, '(', '5'),
|
||||
(0x07, '-', '6'), (0x08, 'è', '7'), (0x09, '_', '8'),
|
||||
(0x0A, 'ç', '9'), (0x0B, 'à', '0'), (0x0C, ')', '°'),
|
||||
(0x0D, '=', '+'), (0x0E, '\x7F', '\x7F'), (0x0F, '\t', '\t'),
|
||||
(0x10, 'a', 'A'), (0x11, 'z', 'Z'), (0x12, 'e', 'E'),
|
||||
(0x13, 'r', 'R'), (0x14, 't', 'T'), (0x15, 'y', 'Y'),
|
||||
(0x16, 'u', 'U'), (0x17, 'i', 'I'), (0x18, 'o', 'O'),
|
||||
(0x19, 'p', 'P'), (0x1A, '^', '¨'), (0x1B, '$', '£'),
|
||||
(0x1C, '\n', '\n'), (0x1E, 'q', 'Q'), (0x1F, 's', 'S'),
|
||||
(0x20, 'd', 'D'), (0x21, 'f', 'F'), (0x22, 'g', 'G'),
|
||||
(0x23, 'h', 'H'), (0x24, 'j', 'J'), (0x25, 'k', 'K'),
|
||||
(0x26, 'l', 'L'), (0x27, 'm', 'M'), (0x28, 'ù', '%'),
|
||||
(0x29, '²', '~'), (0x2B, '*', 'µ'), (0x2C, 'w', 'W'),
|
||||
(0x2D, 'x', 'X'), (0x2E, 'c', 'C'), (0x2F, 'v', 'V'),
|
||||
(0x30, 'b', 'B'), (0x31, 'n', 'N'), (0x32, ',', '?'),
|
||||
(0x33, ';', '.'), (0x34, ':', '/'), (0x35, '!', '§'),
|
||||
(0x01, '\x1B', '\x1B'),
|
||||
(0x02, '&', '1'),
|
||||
(0x03, 'é', '2'),
|
||||
(0x04, '"', '3'),
|
||||
(0x05, '\'', '4'),
|
||||
(0x06, '(', '5'),
|
||||
(0x07, '-', '6'),
|
||||
(0x08, 'è', '7'),
|
||||
(0x09, '_', '8'),
|
||||
(0x0A, 'ç', '9'),
|
||||
(0x0B, 'à', '0'),
|
||||
(0x0C, ')', '°'),
|
||||
(0x0D, '=', '+'),
|
||||
(0x0E, '\x7F', '\x7F'),
|
||||
(0x0F, '\t', '\t'),
|
||||
(0x10, 'a', 'A'),
|
||||
(0x11, 'z', 'Z'),
|
||||
(0x12, 'e', 'E'),
|
||||
(0x13, 'r', 'R'),
|
||||
(0x14, 't', 'T'),
|
||||
(0x15, 'y', 'Y'),
|
||||
(0x16, 'u', 'U'),
|
||||
(0x17, 'i', 'I'),
|
||||
(0x18, 'o', 'O'),
|
||||
(0x19, 'p', 'P'),
|
||||
(0x1A, '^', '¨'),
|
||||
(0x1B, '$', '£'),
|
||||
(0x1C, '\n', '\n'),
|
||||
(0x1E, 'q', 'Q'),
|
||||
(0x1F, 's', 'S'),
|
||||
(0x20, 'd', 'D'),
|
||||
(0x21, 'f', 'F'),
|
||||
(0x22, 'g', 'G'),
|
||||
(0x23, 'h', 'H'),
|
||||
(0x24, 'j', 'J'),
|
||||
(0x25, 'k', 'K'),
|
||||
(0x26, 'l', 'L'),
|
||||
(0x27, 'm', 'M'),
|
||||
(0x28, 'ù', '%'),
|
||||
(0x29, '²', '~'),
|
||||
(0x2B, '*', 'µ'),
|
||||
(0x2C, 'w', 'W'),
|
||||
(0x2D, 'x', 'X'),
|
||||
(0x2E, 'c', 'C'),
|
||||
(0x2F, 'v', 'V'),
|
||||
(0x30, 'b', 'B'),
|
||||
(0x31, 'n', 'N'),
|
||||
(0x32, ',', '?'),
|
||||
(0x33, ';', '.'),
|
||||
(0x34, ':', '/'),
|
||||
(0x35, '!', '§'),
|
||||
(0x39, ' ', ' '),
|
||||
];
|
||||
for &(sc, normal, shifted) in bindings {
|
||||
entries.insert(sc, KeymapEntry { scancode: sc, normal, shifted, altgr: '\0', altgr_shifted: '\0' });
|
||||
entries.insert(
|
||||
sc,
|
||||
KeymapEntry {
|
||||
scancode: sc,
|
||||
normal,
|
||||
shifted,
|
||||
altgr: '\0',
|
||||
altgr_shifted: '\0',
|
||||
},
|
||||
);
|
||||
}
|
||||
let mut dead_keys = Self::common_dead_keys();
|
||||
dead_keys.push(DeadKeyEntry { dead_key: '^', base: ' ', composed: '^' });
|
||||
dead_keys.push(DeadKeyEntry { dead_key: '¨', base: ' ', composed: '¨' });
|
||||
Keymap { name: "azerty".into(), entries, compose: Vec::new(), dead_keys }
|
||||
dead_keys.push(DeadKeyEntry {
|
||||
dead_key: '^',
|
||||
base: ' ',
|
||||
composed: '^',
|
||||
});
|
||||
dead_keys.push(DeadKeyEntry {
|
||||
dead_key: '¨',
|
||||
base: ' ',
|
||||
composed: '¨',
|
||||
});
|
||||
Keymap {
|
||||
name: "azerty".into(),
|
||||
entries,
|
||||
compose: Vec::new(),
|
||||
dead_keys,
|
||||
}
|
||||
}
|
||||
|
||||
fn make_bepo() -> Keymap {
|
||||
let mut entries = HashMap::new();
|
||||
let bindings: &[(u8, char, char)] = &[
|
||||
(0x01, '\x1B', '\x1B'), (0x02, '"', '1'), (0x03, '«', '2'),
|
||||
(0x04, '»', '3'), (0x05, '(', '4'), (0x06, ')', '5'),
|
||||
(0x07, '@', '6'), (0x08, '+', '7'), (0x09, '-', '8'),
|
||||
(0x0A, '/', '9'), (0x0B, '*', '0'), (0x0C, '=', '°'),
|
||||
(0x0D, '%', '`'), (0x0E, '\x7F', '\x7F'), (0x0F, '\t', '\t'),
|
||||
(0x10, 'b', 'B'), (0x11, 'é', 'É'), (0x12, 'p', 'P'),
|
||||
(0x13, 'o', 'O'), (0x14, 'è', 'È'), (0x15, '^', '!'),
|
||||
(0x16, 'v', 'V'), (0x17, 'd', 'D'), (0x18, 'l', 'L'),
|
||||
(0x19, 'j', 'J'), (0x1A, 'z', 'Z'), (0x1B, 'w', 'W'),
|
||||
(0x1C, '\n', '\n'), (0x1E, 'a', 'A'), (0x1F, 'u', 'U'),
|
||||
(0x20, 'i', 'I'), (0x21, 'e', 'E'), (0x22, ',', ';'),
|
||||
(0x23, 'c', 'C'), (0x24, 't', 'T'), (0x25, 's', 'S'),
|
||||
(0x26, 'r', 'R'), (0x27, 'n', 'N'), (0x28, 'm', 'M'),
|
||||
(0x29, 'ç', 'Ç'), (0x2B, '\'', '?'), (0x2C, 'x', 'X'),
|
||||
(0x2D, 'f', 'F'), (0x2E, 'h', 'H'), (0x2F, 'q', 'Q'),
|
||||
(0x30, 'g', 'G'), (0x31, 'y', 'Y'), (0x32, 'k', 'K'),
|
||||
(0x33, '.', ':'), (0x34, '/', '§'), (0x35, 'g', 'G'),
|
||||
(0x01, '\x1B', '\x1B'),
|
||||
(0x02, '"', '1'),
|
||||
(0x03, '«', '2'),
|
||||
(0x04, '»', '3'),
|
||||
(0x05, '(', '4'),
|
||||
(0x06, ')', '5'),
|
||||
(0x07, '@', '6'),
|
||||
(0x08, '+', '7'),
|
||||
(0x09, '-', '8'),
|
||||
(0x0A, '/', '9'),
|
||||
(0x0B, '*', '0'),
|
||||
(0x0C, '=', '°'),
|
||||
(0x0D, '%', '`'),
|
||||
(0x0E, '\x7F', '\x7F'),
|
||||
(0x0F, '\t', '\t'),
|
||||
(0x10, 'b', 'B'),
|
||||
(0x11, 'é', 'É'),
|
||||
(0x12, 'p', 'P'),
|
||||
(0x13, 'o', 'O'),
|
||||
(0x14, 'è', 'È'),
|
||||
(0x15, '^', '!'),
|
||||
(0x16, 'v', 'V'),
|
||||
(0x17, 'd', 'D'),
|
||||
(0x18, 'l', 'L'),
|
||||
(0x19, 'j', 'J'),
|
||||
(0x1A, 'z', 'Z'),
|
||||
(0x1B, 'w', 'W'),
|
||||
(0x1C, '\n', '\n'),
|
||||
(0x1E, 'a', 'A'),
|
||||
(0x1F, 'u', 'U'),
|
||||
(0x20, 'i', 'I'),
|
||||
(0x21, 'e', 'E'),
|
||||
(0x22, ',', ';'),
|
||||
(0x23, 'c', 'C'),
|
||||
(0x24, 't', 'T'),
|
||||
(0x25, 's', 'S'),
|
||||
(0x26, 'r', 'R'),
|
||||
(0x27, 'n', 'N'),
|
||||
(0x28, 'm', 'M'),
|
||||
(0x29, 'ç', 'Ç'),
|
||||
(0x2B, '\'', '?'),
|
||||
(0x2C, 'x', 'X'),
|
||||
(0x2D, 'f', 'F'),
|
||||
(0x2E, 'h', 'H'),
|
||||
(0x2F, 'q', 'Q'),
|
||||
(0x30, 'g', 'G'),
|
||||
(0x31, 'y', 'Y'),
|
||||
(0x32, 'k', 'K'),
|
||||
(0x33, '.', ':'),
|
||||
(0x34, '/', '§'),
|
||||
(0x35, 'g', 'G'),
|
||||
(0x39, ' ', ' '),
|
||||
];
|
||||
for &(sc, normal, shifted) in bindings {
|
||||
entries.insert(sc, KeymapEntry { scancode: sc, normal, shifted, altgr: '\0', altgr_shifted: '\0' });
|
||||
entries.insert(
|
||||
sc,
|
||||
KeymapEntry {
|
||||
scancode: sc,
|
||||
normal,
|
||||
shifted,
|
||||
altgr: '\0',
|
||||
altgr_shifted: '\0',
|
||||
},
|
||||
);
|
||||
}
|
||||
let mut dead_keys = Self::common_dead_keys();
|
||||
dead_keys.push(DeadKeyEntry { dead_key: '^', base: ' ', composed: '^' });
|
||||
Keymap { name: "bepo".into(), entries, compose: Vec::new(), dead_keys }
|
||||
dead_keys.push(DeadKeyEntry {
|
||||
dead_key: '^',
|
||||
base: ' ',
|
||||
composed: '^',
|
||||
});
|
||||
Keymap {
|
||||
name: "bepo".into(),
|
||||
entries,
|
||||
compose: Vec::new(),
|
||||
dead_keys,
|
||||
}
|
||||
}
|
||||
|
||||
fn make_it() -> Keymap {
|
||||
let mut km = Self::make_us();
|
||||
km.name = "it".into();
|
||||
if let Some(e) = km.entries.get_mut(&0x29) { e.normal = '\\'; e.shifted = '|'; }
|
||||
if let Some(e) = km.entries.get_mut(&0x0C) { e.normal = '\''; e.shifted = '?'; }
|
||||
if let Some(e) = km.entries.get_mut(&0x0D) { e.normal = 'ì'; e.shifted = '^'; }
|
||||
if let Some(e) = km.entries.get_mut(&0x1A) { e.normal = 'è'; e.shifted = 'é'; }
|
||||
if let Some(e) = km.entries.get_mut(&0x1B) { e.normal = '+'; e.shifted = '*'; }
|
||||
if let Some(e) = km.entries.get_mut(&0x27) { e.normal = 'ò'; e.shifted = 'ç'; }
|
||||
if let Some(e) = km.entries.get_mut(&0x28) { e.normal = 'à'; e.shifted = '°'; }
|
||||
if let Some(e) = km.entries.get_mut(&0x2B) { e.normal = 'ù'; e.shifted = '§'; }
|
||||
if let Some(e) = km.entries.get_mut(&0x29) {
|
||||
e.normal = '\\';
|
||||
e.shifted = '|';
|
||||
}
|
||||
if let Some(e) = km.entries.get_mut(&0x0C) {
|
||||
e.normal = '\'';
|
||||
e.shifted = '?';
|
||||
}
|
||||
if let Some(e) = km.entries.get_mut(&0x0D) {
|
||||
e.normal = 'ì';
|
||||
e.shifted = '^';
|
||||
}
|
||||
if let Some(e) = km.entries.get_mut(&0x1A) {
|
||||
e.normal = 'è';
|
||||
e.shifted = 'é';
|
||||
}
|
||||
if let Some(e) = km.entries.get_mut(&0x1B) {
|
||||
e.normal = '+';
|
||||
e.shifted = '*';
|
||||
}
|
||||
if let Some(e) = km.entries.get_mut(&0x27) {
|
||||
e.normal = 'ò';
|
||||
e.shifted = 'ç';
|
||||
}
|
||||
if let Some(e) = km.entries.get_mut(&0x28) {
|
||||
e.normal = 'à';
|
||||
e.shifted = '°';
|
||||
}
|
||||
if let Some(e) = km.entries.get_mut(&0x2B) {
|
||||
e.normal = 'ù';
|
||||
e.shifted = '§';
|
||||
}
|
||||
km
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ mod xkb;
|
||||
|
||||
use std::env;
|
||||
use std::io::Write;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use scheme::KeymapScheme;
|
||||
|
||||
@@ -24,13 +22,33 @@ fn main() {
|
||||
Err(_) => "/etc/keymaps".to_string(),
|
||||
};
|
||||
if let Err(e) = scheme.load_from_dir(&keymap_dir) {
|
||||
log_msg("ERROR", &format!("failed to load keymaps from {}: {}", keymap_dir, e));
|
||||
log_msg(
|
||||
"ERROR",
|
||||
&format!("failed to load keymaps from {}: {}", keymap_dir, e),
|
||||
);
|
||||
}
|
||||
|
||||
log_msg("INFO", &format!("loaded {} keymap(s)", scheme.keymap_count()));
|
||||
if let Ok(xkb_root) = env::var("XKB_CONFIG_ROOT") {
|
||||
let layout = env::var("XKB_DEFAULT_LAYOUT").unwrap_or_else(|_| "us".to_string());
|
||||
let variant = env::var("XKB_DEFAULT_VARIANT").ok();
|
||||
if let Err(e) = scheme.load_xkb(&xkb_root, &layout, variant.as_deref()) {
|
||||
log_msg(
|
||||
"ERROR",
|
||||
&format!(
|
||||
"failed to load XKB keymap {} from {}: {}",
|
||||
layout, xkb_root, e
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let socket = redox_scheme::Socket::nonblock("keymap")
|
||||
.expect("keymapd: failed to register scheme:keymap");
|
||||
log_msg(
|
||||
"INFO",
|
||||
&format!("loaded {} keymap(s)", scheme.keymap_count()),
|
||||
);
|
||||
|
||||
let socket =
|
||||
redox_scheme::Socket::create("keymap").expect("keymapd: failed to register scheme:keymap");
|
||||
log_msg("INFO", "registered scheme:keymap");
|
||||
|
||||
loop {
|
||||
@@ -41,15 +59,16 @@ fn main() {
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
log_msg("WARN", &format!("scheme read error (ignoring): {}", e));
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
continue;
|
||||
log_msg("ERROR", &format!("scheme read error: {}", e));
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
match request.handle_scheme_block_mut(&mut scheme) {
|
||||
Ok(response) => {
|
||||
if let Err(e) = socket.write_response(response, redox_scheme::SignalBehavior::Restart) {
|
||||
if let Err(e) =
|
||||
socket.write_response(response, redox_scheme::SignalBehavior::Restart)
|
||||
{
|
||||
log_msg("ERROR", &format!("failed to write response: {}", e));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use syscall::flag::{MODE_DIR, MODE_FILE, SEEK_CUR, SEEK_END, SEEK_SET};
|
||||
|
||||
use crate::keymap::Keymap;
|
||||
|
||||
#[derive(Clone)]
|
||||
enum HandleKind {
|
||||
Root,
|
||||
Active,
|
||||
@@ -69,7 +70,12 @@ impl KeymapScheme {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_xkb(&mut self, xkb_dir: &str, layout: &str, variant: Option<&str>) -> io::Result<()> {
|
||||
pub fn load_xkb(
|
||||
&mut self,
|
||||
xkb_dir: &str,
|
||||
layout: &str,
|
||||
variant: Option<&str>,
|
||||
) -> io::Result<()> {
|
||||
let km = crate::xkb::load_xkb_keymap(xkb_dir, layout, variant)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
||||
let name = match variant {
|
||||
@@ -136,9 +142,12 @@ impl redox_scheme::SchemeBlockMut for KeymapScheme {
|
||||
}
|
||||
|
||||
fn read(&mut self, id: usize, buf: &mut [u8]) -> Result<Option<usize>> {
|
||||
let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?;
|
||||
let (kind, offset) = {
|
||||
let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?;
|
||||
(handle.kind.clone(), handle.offset)
|
||||
};
|
||||
|
||||
let content: Vec<u8> = match &handle.kind {
|
||||
let content: Vec<u8> = match &kind {
|
||||
HandleKind::Root => {
|
||||
let mut listing = String::new();
|
||||
listing.push_str("active\nlist\n");
|
||||
@@ -147,7 +156,12 @@ impl redox_scheme::SchemeBlockMut for KeymapScheme {
|
||||
}
|
||||
listing.into_bytes()
|
||||
}
|
||||
HandleKind::Active => self.active_keymap.clone().into_bytes(),
|
||||
HandleKind::Active => format!(
|
||||
"name={}\nsample_scancode_30={}\n",
|
||||
self.active_keymap,
|
||||
self.translate(0x1E, false, false),
|
||||
)
|
||||
.into_bytes(),
|
||||
HandleKind::List => {
|
||||
let mut listing = String::new();
|
||||
for (i, name) in self.keymaps.keys().enumerate() {
|
||||
@@ -162,20 +176,25 @@ impl redox_scheme::SchemeBlockMut for KeymapScheme {
|
||||
HandleKind::Keymap { name } => {
|
||||
let km = self.keymaps.get(name).ok_or(Error::new(ENOENT))?;
|
||||
format!(
|
||||
"name={}\nentries={}\ncompose={}\ndead_keys={}\n",
|
||||
"name={}\nentries={}\ncompose={}\ndead_keys={}\nsample_scancode_30={}\nsample_altgr_scancode_30={}\nsample_compose_a_acute={}\nsample_compose_sequence={}\n",
|
||||
km.name,
|
||||
km.entries.len(),
|
||||
km.compose.len(),
|
||||
km.dead_keys.len()
|
||||
km.dead_keys.len(),
|
||||
km.get_char(0x1E, false, false),
|
||||
km.get_char(0x1E, false, true),
|
||||
km.compose('a', '\''),
|
||||
km.lookup_compose("compose").unwrap_or('\0'),
|
||||
)
|
||||
.into_bytes()
|
||||
}
|
||||
};
|
||||
|
||||
if handle.offset >= content.len() {
|
||||
let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?;
|
||||
if offset >= content.len() {
|
||||
return Ok(Some(0));
|
||||
}
|
||||
let remaining = &content[handle.offset..];
|
||||
let remaining = &content[offset..];
|
||||
let to_copy = remaining.len().min(buf.len());
|
||||
buf[..to_copy].copy_from_slice(&remaining[..to_copy]);
|
||||
handle.offset += to_copy;
|
||||
|
||||
@@ -210,10 +210,22 @@ struct XkbKeyEntry {
|
||||
}
|
||||
|
||||
fn parse_keysyms(syms: &[&str]) -> (char, char, char, char) {
|
||||
let normal = syms.get(0).map(|s| keysym_to_char(s.trim())).unwrap_or('\0');
|
||||
let shifted = syms.get(1).map(|s| keysym_to_char(s.trim())).unwrap_or('\0');
|
||||
let altgr = syms.get(2).map(|s| keysym_to_char(s.trim())).unwrap_or('\0');
|
||||
let altgr_shifted = syms.get(3).map(|s| keysym_to_char(s.trim())).unwrap_or('\0');
|
||||
let normal = syms
|
||||
.get(0)
|
||||
.map(|s| keysym_to_char(s.trim()))
|
||||
.unwrap_or('\0');
|
||||
let shifted = syms
|
||||
.get(1)
|
||||
.map(|s| keysym_to_char(s.trim()))
|
||||
.unwrap_or('\0');
|
||||
let altgr = syms
|
||||
.get(2)
|
||||
.map(|s| keysym_to_char(s.trim()))
|
||||
.unwrap_or('\0');
|
||||
let altgr_shifted = syms
|
||||
.get(3)
|
||||
.map(|s| keysym_to_char(s.trim()))
|
||||
.unwrap_or('\0');
|
||||
(normal, shifted, altgr, altgr_shifted)
|
||||
}
|
||||
|
||||
@@ -242,15 +254,15 @@ pub fn parse_xkb_symbols(content: &str, variant: Option<&str>) -> Result<Keymap,
|
||||
}
|
||||
if inner.starts_with("key <") {
|
||||
if let Some(entry) = parse_key_line(inner) {
|
||||
entries.entry(entry.scancode).or_insert_with(|| {
|
||||
KeymapEntry {
|
||||
entries
|
||||
.entry(entry.scancode)
|
||||
.or_insert_with(|| KeymapEntry {
|
||||
scancode: entry.scancode,
|
||||
normal: entry.normal,
|
||||
shifted: entry.shifted,
|
||||
altgr: entry.altgr,
|
||||
altgr_shifted: entry.altgr_shifted,
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
@@ -260,7 +272,10 @@ pub fn parse_xkb_symbols(content: &str, variant: Option<&str>) -> Result<Keymap,
|
||||
}
|
||||
|
||||
if !found_variant {
|
||||
return Err(format!("variant '{}' not found in XKB symbols file", target_variant));
|
||||
return Err(format!(
|
||||
"variant '{}' not found in XKB symbols file",
|
||||
target_variant
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Keymap {
|
||||
@@ -316,7 +331,11 @@ fn parse_key_line(line: &str) -> Option<XkbKeyEntry> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn load_xkb_keymap(xkb_dir: &str, layout: &str, variant: Option<&str>) -> Result<Keymap, String> {
|
||||
pub fn load_xkb_keymap(
|
||||
xkb_dir: &str,
|
||||
layout: &str,
|
||||
variant: Option<&str>,
|
||||
) -> Result<Keymap, String> {
|
||||
let file_path = format!("{}/symbols/{}", xkb_dir, layout);
|
||||
let content = std::fs::read_to_string(&file_path)
|
||||
.map_err(|e| format!("failed to read XKB symbols file '{}': {}", file_path, e))?;
|
||||
|
||||
@@ -7,18 +7,38 @@ use std::os::unix::process::CommandExt;
|
||||
use std::process::Command;
|
||||
|
||||
use libredox::Fd;
|
||||
use redox_scheme::Socket;
|
||||
use redox_scheme::scheme::{SchemeAsync, SchemeSync};
|
||||
use redox_scheme::Socket;
|
||||
|
||||
unsafe fn get_fd(var: &str) -> RawFd {
|
||||
let fd: RawFd = std::env::var(var).unwrap().parse().unwrap();
|
||||
unsafe fn get_fd(var: &str) -> Option<RawFd> {
|
||||
let value = match std::env::var(var) {
|
||||
Ok(value) => value,
|
||||
Err(_) => {
|
||||
let exe = std::env::args()
|
||||
.next()
|
||||
.unwrap_or_else(|| "daemon".to_string());
|
||||
eprintln!("daemon: {var} not set for {exe}; readiness notification disabled");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let fd: RawFd = match value.parse() {
|
||||
Ok(fd) => fd,
|
||||
Err(err) => {
|
||||
let exe = std::env::args()
|
||||
.next()
|
||||
.unwrap_or_else(|| "daemon".to_string());
|
||||
eprintln!("daemon: invalid {var} value {value:?} for {exe}: {err}; readiness notification disabled");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
if unsafe { libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC) } == -1 {
|
||||
panic!(
|
||||
eprintln!(
|
||||
"daemon: failed to set CLOEXEC flag for {var} fd: {}",
|
||||
io::Error::last_os_error()
|
||||
);
|
||||
return None;
|
||||
}
|
||||
fd
|
||||
Some(fd)
|
||||
}
|
||||
|
||||
unsafe fn pass_fd(cmd: &mut Command, env: &str, fd: OwnedFd) {
|
||||
@@ -38,20 +58,22 @@ unsafe fn pass_fd(cmd: &mut Command, env: &str, fd: OwnedFd) {
|
||||
/// A long running background process that handles requests.
|
||||
#[must_use = "Daemon::ready must be called"]
|
||||
pub struct Daemon {
|
||||
write_pipe: PipeWriter,
|
||||
write_pipe: Option<PipeWriter>,
|
||||
}
|
||||
|
||||
impl Daemon {
|
||||
/// Create a new daemon.
|
||||
pub fn new(f: impl FnOnce(Daemon) -> !) -> ! {
|
||||
let write_pipe = unsafe { io::PipeWriter::from_raw_fd(get_fd("INIT_NOTIFY")) };
|
||||
let write_pipe = unsafe { get_fd("INIT_NOTIFY").map(io::PipeWriter::from_raw_fd) };
|
||||
|
||||
f(Daemon { write_pipe })
|
||||
}
|
||||
|
||||
/// Notify the process that the daemon is ready to accept requests.
|
||||
pub fn ready(mut self) {
|
||||
self.write_pipe.write_all(&[0]).unwrap();
|
||||
if let Some(write_pipe) = self.write_pipe.as_mut() {
|
||||
write_pipe.write_all(&[0]).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes `Command` as a child process.
|
||||
@@ -83,25 +105,27 @@ impl Daemon {
|
||||
/// A long running background process that handles requests using schemes.
|
||||
#[must_use = "SchemeDaemon::ready must be called"]
|
||||
pub struct SchemeDaemon {
|
||||
write_pipe: PipeWriter,
|
||||
write_pipe: Option<PipeWriter>,
|
||||
}
|
||||
|
||||
impl SchemeDaemon {
|
||||
/// Create a new daemon for use with schemes.
|
||||
pub fn new(f: impl FnOnce(SchemeDaemon) -> !) -> ! {
|
||||
let write_pipe = unsafe { io::PipeWriter::from_raw_fd(get_fd("INIT_NOTIFY")) };
|
||||
let write_pipe = unsafe { get_fd("INIT_NOTIFY").map(io::PipeWriter::from_raw_fd) };
|
||||
|
||||
f(SchemeDaemon { write_pipe })
|
||||
}
|
||||
|
||||
/// Notify the process that the scheme daemon is ready to accept requests.
|
||||
pub fn ready_with_fd(self, cap_fd: Fd) -> syscall::Result<()> {
|
||||
syscall::call_wo(
|
||||
self.write_pipe.as_raw_fd() as usize,
|
||||
&cap_fd.into_raw().to_ne_bytes(),
|
||||
syscall::CallFlags::FD,
|
||||
&[],
|
||||
)?;
|
||||
if let Some(write_pipe) = self.write_pipe {
|
||||
syscall::call_wo(
|
||||
write_pipe.as_raw_fd() as usize,
|
||||
&cap_fd.into_raw().to_ne_bytes(),
|
||||
syscall::CallFlags::FD,
|
||||
&[],
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ git = "https://gitlab.redox-os.org/redox-os/base.git"
|
||||
rev = "463f76b9608a896e6f6c9f63457f57f6409873c7"
|
||||
patches = [
|
||||
"P0-daemon-fix-init-notify-unwrap.patch",
|
||||
"P0-daemon-silence-init-notify.patch",
|
||||
"P0-workspace-add-bootstrap.patch",
|
||||
"P0-init-continuous-scheduling.patch",
|
||||
# TODO: P6-e1000d-msi-migration.patch conflicts with P6-driver-main-fixes
|
||||
|
||||
@@ -36,7 +36,7 @@ patches = [
|
||||
"P3-inet6-pton-ntop.patch",
|
||||
"P3-tcp-nodelay.patch",
|
||||
"P3-tcp-sockopt-forward.patch",
|
||||
# Named POSIX semaphores (sem_open/close/unlink) — comprehensive implementation
|
||||
# Named POSIX semaphores (sem_open/close/unlink) — comprehensive + refcount + va_list
|
||||
"P3-semaphore-comprehensive.patch",
|
||||
# Reverse From<&syscall::TimeSpec> for timespec (needed by getrusage)
|
||||
"P3-timespec-reverse-from.patch",
|
||||
|
||||
Reference in New Issue
Block a user