Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ the contract is "it works on RS50 and G Pro as listed here".

## Unreleased

**The DirectInput proxy is aimed at the wheel, says why it failed, and can
open `/dev/uhid` on a fresh package install.** Three faults from one report
([#105](../../issues/105)). The launcher's lookup of the wheel's USB parent,
used to aim `logi-ffb` at the session's wheel, resolved `..` through a
sysfs symlink logically and landed in `/sys/bus/hid`, so the aim never
fired on any machine and the proxy fell back to its own scan. Fixed with a
real path. The proxy's messages, including the reason it refused to start,
went to Steam's console; they now also land in the launcher log next to the
plan that started it. And the Debian and RPM packages never reloaded udev
after installing their rules, so `/dev/uhid` kept its root-only mode until
the next reboot and the proxy could not create its virtual wheel; both now
reload and re-trigger (Arch's own package hook already does this, and the
from-source installer always did).

**Steam no longer thinks a game is still running after it exits.** Steam
runs the launch command under a subreaper, so a process the launcher started
and left behind was handed to Steam when the game exited, and Steam kept the
Expand Down
7 changes: 7 additions & 0 deletions packaging/akmods/logitech-trueforce-kmod.spec
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,13 @@ force-feedback device).

%post -n %{kmod_name}-kmod-common
%{_bindir}/logi-wheel-initramfs >/dev/null 2>&1 || :
# udev rules only reach devices that appear after they load: reload and
# re-trigger so /dev/uhid and an already-plugged wheel pick them up now
# rather than at the next reboot (#105). Never fatal.
if command -v udevadm >/dev/null 2>&1; then
udevadm control --reload >/dev/null 2>&1 || :
udevadm trigger --subsystem-match=misc --subsystem-match=hidraw --subsystem-match=input >/dev/null 2>&1 || :
fi

%files -n %{kmod_name}-kmod-common
%{_bindir}/logi-wheel-initramfs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@ post_install() {
(or point --sdk-dir / $LOGITECH_TRUEFORCE_SDK_DIR elsewhere)
2. Run as your normal user (NOT root):
logi-shim --all-steam
3. Per SDK sim in Steam: set launch option PROTON_ENABLE_HIDRAW=1
and disable Steam Input for the wheel.
3. Per game in Steam: launch option `logi-launch %command%` and
Steam Input off. The launcher works out raw HID, the proxies
and the daemon per game and per wheel; never set
PROTON_ENABLE_HIDRAW by hand, on a G923 it costs the force
feedback you have.

Full guide: https://github.com/mescon/logitech-trueforce-linux-driver/wiki/Installation
MSG
Expand Down
11 changes: 11 additions & 0 deletions packaging/debian/logitech-trueforce-dkms.postinst
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,15 @@ if [ "$1" = "configure" ] && [ -x /usr/bin/logi-wheel-initramfs ]; then
/usr/bin/logi-wheel-initramfs || echo "warning: could not update the initramfs; run logi-wheel-initramfs by hand" >&2
fi

# The udev rules this package installs only take effect for devices that
# appear after they are loaded. /dev/uhid exists from boot, so without a
# reload and a trigger it keeps its root-only mode until the next reboot
# and the logi-ffb proxy cannot create its virtual wheel (#105). The same
# applies to a wheel that was already plugged in. Never fatal: a system
# without a running udev (a container, a chroot) installs fine.
if [ "$1" = "configure" ] && command -v udevadm >/dev/null 2>&1; then
udevadm control --reload 2>/dev/null || true
udevadm trigger --subsystem-match=misc --subsystem-match=hidraw --subsystem-match=input 2>/dev/null || true
fi

exit 0
7 changes: 7 additions & 0 deletions packaging/obs/logitech-trueforce-dkms.spec
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,13 @@ fi
# Into the initramfs as well, so it registers before the in-tree driver can
# claim a G923 at boot (#90); a no-op note when the module is not built yet.
%{_bindir}/logi-wheel-initramfs >/dev/null 2>&1 || :
# udev rules only reach devices that appear after they load: reload and
# re-trigger so /dev/uhid and an already-plugged wheel pick them up now
# rather than at the next reboot (#105). Never fatal.
if command -v udevadm >/dev/null 2>&1; then
udevadm control --reload >/dev/null 2>&1 || :
udevadm trigger --subsystem-match=misc --subsystem-match=hidraw --subsystem-match=input >/dev/null 2>&1 || :
fi

%preun
dkms remove -m %{module} -v %{modver} --all --rpm_safe_upgrade >/dev/null 2>&1 || true
Expand Down
10 changes: 9 additions & 1 deletion tools/logi-launch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ EXTRA_HELPERS="${LOGI_LAUNCH_HELPERS:-}"
WAIT_SECONDS="${LOGI_LAUNCH_WAIT:-120}"
SETTLE_SECONDS="${LOGI_LAUNCH_SETTLE:-15}"
LOG="${LOGI_LAUNCH_LOG:-/tmp/logi-launch.log}"
# Shared with the tools this wrapper starts (logi-ffb reads it), so what
# they have to say lands in the same file as the plan that started them
# rather than on Steam's console, where nobody looks (#105).
export LOGI_LAUNCH_LOG="$LOG"

# Where the Windows-side pieces we stage into games live: the dinput8
# escape proxy, the telemetry relay, the recorded init burst.
Expand Down Expand Up @@ -443,7 +447,11 @@ resolve_wheel() {
if [ -n "$wheel_dir" ]; then
# ../.. from the HID device directory is the USB device: the wheel's
# interfaces (hidraw here, input there) are siblings under it.
wheel_usb=$(cd "$wheel_dir/../.." 2>/dev/null && pwd -P) || wheel_usb=""
# readlink -f, not cd: /sys/bus/hid/devices/<id> is a symlink, and
# the shell's cd resolves ".." logically, so "cd <id>/../.." landed
# in /sys/bus/hid and nothing below matched. The proxy was never
# aimed on any machine and fell back to its own scan (#105).
wheel_usb=$(readlink -f "$wheel_dir/../.." 2>/dev/null) || wheel_usb=""
for e in /sys/class/input/event*; do
[ -d "$e/device" ] || continue
[ -n "$wheel_usb" ] || break
Expand Down
10 changes: 5 additions & 5 deletions userspace/logi-wheel/crates/logi-ffb/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,11 @@ fn run_game(cmd: Vec<String>) -> crate::Result<ExitCode> {
std::env::var("STEAM_COMPAT_DATA_PATH").ok().as_deref(),
);
match &prefix {
Some(p) => eprintln!("logi-ffb: hiding {name:?} from DirectInput in {p}"),
None => eprintln!(
Some(p) => crate::note(&format!("logi-ffb: hiding {name:?} from DirectInput in {p}")),
None => crate::note(&format!(
"logi-ffb: no Wine prefix known (WINEPREFIX and STEAM_COMPAT_DATA_PATH unset); a DirectInput game may list the real wheel too, bind it to {:?}",
crate::descriptor::VIRTUAL_NAME
),
)),
}
steering::apply(&plan, prefix.as_deref())?;

Expand Down Expand Up @@ -181,8 +181,8 @@ fn run_game(cmd: Vec<String>) -> crate::Result<ExitCode> {

match join_result {
Ok(Ok(())) => {}
Ok(Err(e)) => eprintln!("logi-ffb: proxy loop error: {e}"),
Err(_) => eprintln!("logi-ffb: proxy thread panicked"),
Ok(Err(e)) => crate::note(&format!("logi-ffb: proxy loop error: {e}")),
Err(_) => crate::note("logi-ffb: proxy thread panicked"),
}

Ok(ExitCode::from(status.code().unwrap_or(0) as u8))
Expand Down
43 changes: 43 additions & 0 deletions userspace/logi-wheel/crates/logi-ffb/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,49 @@ pub mod cli;

use std::fmt;

/// Say something to the person running this: on stderr, and also appended
/// to the file `LOGI_LAUNCH_LOG` names when that is set. The launcher sets
/// it to its own log so that a proxy that refuses to start explains itself
/// next to the plan that started it. Under Steam, stderr goes to a console
/// nobody reads, and a game that "did not start" came with no reason
/// attached (#105). Never fails: a log that cannot be written is not a
/// reason to lose the message on stderr.
pub fn note(msg: &str) {
eprintln!("{msg}");
if let Some(path) = std::env::var_os("LOGI_LAUNCH_LOG") {
note_to(std::path::Path::new(&path), msg);
}
}

/// The file half of [`note`], separated so it can be tested without
/// touching the process environment.
pub fn note_to(path: &std::path::Path, msg: &str) {
use std::io::Write;
if let Ok(mut f) = std::fs::OpenOptions::new().append(true).create(true).open(path) {
let _ = writeln!(f, "{msg}");
}
}

#[cfg(test)]
mod note_tests {
#[test]
fn note_to_appends_one_line_per_call() {
let dir = std::env::temp_dir().join(format!("logi-ffb-note-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let log = dir.join("launch.log");
super::note_to(&log, "logi-ffb: first");
super::note_to(&log, "logi-ffb: second");
let text = std::fs::read_to_string(&log).unwrap();
assert_eq!(text, "logi-ffb: first\nlogi-ffb: second\n");
let _ = std::fs::remove_dir_all(&dir);
}

#[test]
fn note_to_an_unwritable_path_is_silent() {
super::note_to(std::path::Path::new("/proc/no-such-dir/launch.log"), "dropped");
}
}

#[derive(Debug)]
pub enum Error {
/// The real wheel's evdev FF node could not be found.
Expand Down
2 changes: 1 addition & 1 deletion userspace/logi-wheel/crates/logi-ffb/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ fn main() -> ExitCode {
match logi_ffb::cli::dispatch(&args) {
Ok(code) => code,
Err(e) => {
eprintln!("logi-ffb: {e}");
logi_ffb::note(&format!("logi-ffb: {e}"));
ExitCode::FAILURE
}
}
Expand Down
6 changes: 3 additions & 3 deletions userspace/logi-wheel/crates/logi-ffb/src/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,11 +202,11 @@ fn warn_if_virtual_wheel_hidraw_inaccessible() {
if let Some(node) = find_virtual_wheel_hidraw_node() {
let dev_path = format!("/dev/{node}");
if access(dev_path.as_str(), AccessFlags::R_OK | AccessFlags::W_OK).is_err() {
eprintln!(
crate::note(&format!(
"logi-ffb: warning: the virtual wheel's hidraw node ({dev_path}) is not \
user-accessible; DirectInput force feedback will not work. Install the \
71-logi-ffb-uhid.rules udev rule (issue #50)."
);
));
}
return;
}
Expand Down Expand Up @@ -267,7 +267,7 @@ pub struct Proxy {
fn run_sink_worker(mut sink: sink::Sink, rx: std::sync::mpsc::Receiver<pidff::EffectOp>) {
for op in &rx {
if let Err(e) = sink.apply(op) {
eprintln!("logi-ffb: failed to apply FF operation: {e}");
crate::note(&format!("logi-ffb: failed to apply FF operation: {e}"));
}
}
sink.shutdown();
Expand Down
Loading