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:
2026-05-05 21:12:08 +01:00
parent f31522130f
commit 702ec7efac
14 changed files with 1201 additions and 279 deletions
@@ -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))?;