Conversation
C0rn3j
force-pushed
the
sc2
branch
3 times, most recently
from
August 2, 2026 21:11
f4dae15 to
ce8d822
Compare
C0rn3j
force-pushed
the
main
branch
2 times, most recently
from
August 9, 2026 14:38
69fd146 to
2178249
Compare
C0rn3j
force-pushed
the
sc2
branch
2 times, most recently
from
August 13, 2026 17:11
b4bff7e to
d2a6658
Compare
C0rn3j
force-pushed
the
sc2
branch
7 times, most recently
from
August 17, 2026 20:04
459b960 to
433feeb
Compare
C0rn3j
force-pushed
the
sc2
branch
10 times, most recently
from
August 25, 2026 19:22
0dd419c to
e1cff44
Compare
C0rn3j
force-pushed
the
main
branch
3 times, most recently
from
August 26, 2026 10:57
1f8b60c to
4c804d4
Compare
Refs #7. Defect introduced in 0645782. Splitting FF_RUMBLE's two magnitudes out of the averaged level made the mapper hand controller.rumble() strong and weak directly -- but only FF_RUMBLE fills those. Every other effect type carries just 'level', so constant, periodic, spring and damper effects arrived as rumble(0, 0), which a controller with real motors answers True to. The effect was then swallowed: silent, and no fall-through to the click-train emulation either. Fall back to the averaged level whenever both magnitudes are zero and the level is not, which covers the other effect types and any libuinput built before the struct grew those fields. Also default strong/weak on upload, next to level's own default. They are written only in the FF_RUMBLE branch while the effect slots are pooled and reused, so a slot that previously held a rumble effect could leak its magnitudes into an effect of a different type.
The two FF_RUMBLE magnitudes are arriving as zero even though uinput.c stores them on upload, the object was recompiled, and both the C struct and its ctypes mirror agree at 24 bytes with strong@20 / weak@22. fftest #4 and #5 produce 10922 and 8192, which are exactly strong/3 and weak/6 -- the averaged level -- so the fallback added in d18e465 is being taken. Static inspection has run out, so log the effect exactly as the shim hands it over. This separates a shim-side problem (magnitudes never stored, or stored in a slot other than the one played) from a driver-side one.
Rumble confirmed live with fftest on the emulated pad: type, intensity and both gains at 0, and the two speed fields driving their own actuator independently, with Linux's strong/weak FF_RUMBLE magnitudes landing on left and right as Valve's SDL3 driver arranges them. Also records an open question the test raised. The field is called speed, not amplitude, and gain is a separate i8 in dB. The weak-motor effect carries the larger value (49152 against 32768) yet is clearly weaker in the hand, so speed looks like a motor speed rather than a linear amplitude -- higher meaning a lighter, higher-frequency buzz. Both gains are 0 dB only because that is what SDL sends; per-side gain and the intensity field are untried and may be the better volume control.
Refs #7. The synthesised effects from the v2's haptic family, output reports 0x83 (LFO tone), 0x84 (logarithmic sweep) and 0x85 (firmware script). Two design decisions, both about not breaking what exists: HapticData grows the effect and its parameters as defaulted keyword arguments, kept OUT of the 'data' tuple. That tuple is unpacked positionally by every existing driver, so growing it would break them; the new fields are plain attributes that drivers read only if they understand them. feedback() is left bit-for-bit compatible, because it appears in every profile already written and its arguments are positional -- adding to it would change the meaning of files on disk. The new effects are sibling commands instead: feedbacktone, feedbacksweep and feedbackscript, sharing a base class with feedback() so they attach to an action the same way. Only hardware that can synthesise waveforms plays them, currently the v2 alone. Anything else ignores the effect field and plays its usual click, so a profile written for one controller still does something on another rather than going silent. Report layouts verified against the documented sizes: tone 10 bytes, sweep 9, script 4. TONE -> 83 01 fe dc 00 2c 01 04 00 80 SWEEP -> 84 00 fe fa 00 90 01 50 00 SCRIPT -> 85 01 03 fe Also fixes HapticData.with_position, which passed self.frequency back to the constructor even though it is already scaled by 1000 -- so it was multiplied again on every copy, and send_feedback() copies for every HapticPos.BOTH effect. And drops ControllerFlags from modifiers.py, unused since the pad/ stick predicate replaced its last reader. The repo requires docs and both a parser and a profile test for every action; all three are provided for each new command.
Refs #7. Front end for the tone/sweep/script effects added in 7086d91. The Feedback panel gains an Effect chooser above the side selector, since it decides what the rest of the panel means, and the parameter rows change with it: tone gets frequency, duration and the two LFO controls; sweep gets start and end frequency plus duration; preset gets a script id. Strength stays throughout, and the two click-only sliders -- pad-travel frequency and click period, neither of which means anything to a synthesised effect -- are hidden unless Click is selected. The chooser is hidden entirely on controllers that cannot synthesise waveforms, rather than offering effects that would quietly degrade to a click. Currently that means it appears for the v2 only. Those rows are built in code rather than in the .glade. Six rows of label+scale that are mostly hidden at any one time is a lot of XML to keep correct by hand, and building them from a table keeps the rows, the show/hide rule and the modifier arguments in step. Each effect modifier now declares its own PARAMS, and the editor builds the modifier positionally from that. Keeping the order in the GUI instead meant two lists that had to agree, and they already did not: FeedbackScriptModifier took (position, script_id, amplitude) while the editor passed amplitude second, so choosing a preset would have written the strength into the script id. Its signature now matches its siblings, and a test asserts PARAMS against each _mod_init signature so the two cannot drift again. That test deliberately imports only the modifiers. Importing the GUI from the suite loads Gtk without a version request and breaks the version negotiation in test_setup, which is how the first version of it failed.
The effect chooser was driven by a table in action_editor.py that paired each
HapticEffect with its modifier, and read .PARAMS off whichever it found. Only
the three new effect modifiers had that attribute; FeedbackModifier, which
carries plain Click, did not. Reading it raised during setup_widgets, so the
action editor failed to construct at all -- every binding, not just haptic
ones:
AttributeError: type object 'FeedbackModifier' has no attribute 'PARAMS'
Give Click the same declarations as the others (it uses none of the extra
parameter rows, its knobs being feedback()'s own frequency and period, but it
still has to say so), and move the table to modifiers.py as
HAPTIC_EFFECT_MODIFIERS.
The move is the actual fix. The table lived in a module the test suite cannot
import -- importing the GUI loads Gtk without a version request and breaks
version negotiation in test_setup -- so nothing could check it, and a missing
attribute on one entry only showed up by opening the editor. In modifiers.py
it is plain data: two tests now walk every entry and assert each declares
EFFECT, PARAMS, LABEL and COMMAND with no duplicate effects, and that every
name in PARAMS is a field HapticData actually carries. Verified that removing
PARAMS again fails them.
Switching effects reset the parameters, and confirming the dialog came back with feedback disabled -- sometimes losing unrelated settings such as the mouse output. Preset lost them every time. update_modifiers() is what decides an action is dirty: it diffs every widget against state held on the editor and, on any difference, rebuilds by calling set_action(). The effect chooser and its parameter rows were not in that state. So changing them marked nothing dirty, and the next rebuild triggered by anything else regenerated the action from state that had never heard of them -- taking the effect, its parameters and whatever else was mid-edit. Track feedback_effect and feedback_params alongside feedback_position, diff them like everything else, and generate from the tracked values rather than reading the widgets directly. Loading an action now restores the state as well as the widgets, including resetting to Click, which a tone loaded before a click action would otherwise have kept. Adds tests/test_action_editor.py, which constructs the real editor against a stub app and exercises this. The GUI had no tests because importing it loads Gtk, and both bugs shipped this round would have been caught by opening the editor once -- so the tests skip without a display and run under xvfb-run, rather than not existing. They already earned it: they caught an off-by-one reading the parameter defaults tuple.
The preset row was a bare 0-255 slider because I had assumed the script ids were undocumented -- and then suggested probing "0 to 5", a number I had carried over from an fftest effect list that has nothing to do with them. They are documented: 16 named presets, 0x01 to 0x10, from the same reverse engineering the rest of the haptic family came from. So the row is a dropdown of names, and the ids are recorded in the protocol doc. Names are unverified -- what each actually feels like is still to be checked on hardware -- and the list may be partial, so an id we do not know is kept and shown as "Preset N" rather than silently rewritten to something we do. Parameter rows can now be either a dropdown or a slider, for parameters that enumerate rather than scale, read and written through accessors so the rest of the editor does not care which. Also records in TODO why button presses still have no feedback option: it is not an oversight, ButtonAction simply never grew set_haptic, and adding MOD_FEEDBACK to it changes what the editor offers for buttons on every controller -- so it wants its own pass.
Opening a saved binding brought up an editor with no action in it:
output "None", feedback switched off, side back to left, and only the
effect itself preserved.
load_modifiers() has two halves. The first parses the modifier chain
into editor state; the second writes that state into the widgets, and it
is guarded by _recursing because every widget write fires a 'changed'
handler that calls update_modifiers(). The effect chooser and its
parameter rows were being written in the FIRST half, unguarded, so:
- the combo emits 'changed' -> update_modifiers() runs
- it reads cbFeedback, still unchecked because that widget is not
written until much further down, and concludes feedback is off
- it marks the action dirty and calls set_action(self._action), which
on a freshly opened editor is None
The editor then loads None and shows the first page, and the action
being edited never arrives. Which half of the panel survived told the
story: the effect was kept because it was the one thing already written.
Fixed by moving those widget writes down into the guarded block with
every other one. The parse loop now only records state.
Covered by test_opening_the_editor_keeps_the_feedback, which goes
through set_input() -- the entry point the dialog actually uses.
test_loading_an_effect_restores_the_widgets went the same way; it had
been setting _recursing by hand, and that fake is exactly what hid this.
Refs Patola#7
Co-Authored-By: Claude <noreply@anthropic.com>
A slider spanning 20-1000 Hz in about 200 pixels is roughly 5 Hz per pixel, so there was no way to ask for 220 Hz specifically -- and the arrow keys moved in jumps of 10, because the per-parameter step was being used as the adjustment's step_increment. Each numeric row is now a slider and a spin button sharing one Gtk.Adjustment: drag to explore the range by feel, type or click the spin arrows to land on a value exactly. Sharing the adjustment is what keeps them honest -- there is no synchronising code that could get it wrong, and the 'value-changed' handler hangs off the adjustment rather than either widget. The slider no longer draws its own number, since the spin button is already showing it. Arrow keys and the spin button now always step by 1. The table's step became the page increment instead, for Page Up/Down and trough clicks: 10 Hz for the frequencies, 50 ms for duration. Preset is untouched, it is a named dropdown. Wrapping each row in a Box made it reachable by show_all(), which would have unhidden every parameter row at once, so hidden rows are marked no-show-all. The new assertion in test_effect_rows_follow_the_chosen_effect fails without it. Refs Patola#7 Co-Authored-By: Claude <noreply@anthropic.com>
Reports 0x83, 0x84 and 0x85 are now verified live, not just read out of
the SDL3 source and iczero's RE:
- a sweep is clearly distinguishable from a flat tone, so start_freq
and end_freq do what they claim
- the LFO fields work but are weak. At lfo_freq 4 / lfo_depth 128 the
modulation is barely perceptible; only at 10 / 255 is it obvious, so
depth is probably not a percentage
- the preset ids are real and their RE'd names broadly describe what
you feel. CONTROLLER_VERY_ON, CONTROLLER_OFF and PHONE_RINGING_1 are
strong and unmistakable; several others are subtle
- WILHELM_SCREAM is barely perceptible even at maximum gain. It is a
long sample, so this is most likely the preset itself rather than
anything we send -- worth knowing before someone goes hunting for a
bug in the gain path
Nothing was found past 0x10, but nothing rules it out either; the list
is still recorded as possibly partial.
Refs Patola#7
Co-Authored-By: Claude <noreply@anthropic.com>
With Trackball Mode on, the sensitivity sliders did nothing at all on a DS4 right stick. A ball on a self-centering stick is meaningless -- it works on position deltas, so holding the stick still stops the pointer and releasing it drags the pointer back -- so BallModifier.whole passes RIGHT straight through when that slot carries stick data. But SensitivityModifier walks down to the first action with set_speed() and stops there, which on a trackball binding is the ball, and the ball applies its speed only in _add and _roll: both on the path it just skipped. The speed landed on a modifier that had already stepped aside, and the child stayed at 1.0. The ball now hands its speed to the child before bypassing, multiplied into whatever the child already has (so it is right whichever side of the ball the sens() ended up on) and once only, since set_speed is absolute. It must not happen when the ball is really running, or the speed would be applied twice; that is asserted. Also: BallModifier.compress overrode Modifier.compress and dropped its child-recursion, so a sens() written INSIDE a ball survived as a live SensitivityModifier -- which has no whole(), so the binding did nothing whatsoever. The GUI always writes sens() outside, so only hand-written profiles could hit it. Restored, keeping the ball(circular(...)) turn-around it overrode compress() for in the first place. NOTE: the compress change affects every controller, not just the DS4. Refs Patola#5 Co-Authored-By: Claude <noreply@anthropic.com>
The Strength slider stopped at 32639 instead of 32767, which reads as an off-by-something in the code rather than as a limit -- and it silently capped the range every haptic strength measurement was taken over. A GtkAdjustment's usable maximum is upper - page_size, and page_size means nothing for a slider. adjFAmplitude and adjFPeriod both carried 128. Guarded by pinning the amplitude slider's range to the driver's own HAPTIC_AMPLITUDE_MIN/MAX rather than banning the property outright: some adjustments use it deliberately (global_settings sets upper 10.01 with page-size 0.01 to land on a round 10.00). Co-Authored-By: Claude <noreply@anthropic.com>
README: a DS4/DS5 cannot be claimed exclusively over Bluetooth. A Steam Controller is taken by claiming its USB interface, which detaches the kernel driver; a PlayStation pad over Bluetooth is reached through /dev/hidrawN, and hidraw allows several readers at once. With Steam running, Steam and SC Controller both receive every report and both act on it, and no code of ours can prevent that. Written symptoms-first, because the failure does not look like a conflict: the touchpad glides with an acceleration nobody configured, its click gives a mouse button whatever you bind, and yet your other bindings work fine -- so it reads as half-broken configuration rather than as a second program driving the pad. That misreading cost three rounds of debugging here before someone noticed Steam holding the hidraw node. TODO: haptic feedback on the DS4 is not implemented at all -- all three DS4 controller classes inherit a no-op feedback(), and EvdevController's is an empty TODO docstring. ds5drv.feedback is close to a template, with two things not to copy blindly, and Controller.rumble() is unimplemented for both pads, which is why fftest's heavy and light effects feel identical on them. Co-Authored-By: Claude <noreply@anthropic.com>
The daemon was calling os.umask(0) after daemonizing, which left all subsequently created files (including the IPC socket) world-accessible. Change to os.umask(0o077) so files are owner-only by default. For the IPC socket, call os.chmod() to enforce 0600 immediately after bind() and before serve_forever() starts accepting connections, ensuring there is no window where the socket is accessible to other users. Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <scorreia@redhat.com>
The Bluetooth long-packet reassembly extracts a 4-bit packet number from the incoming data (range 0-15) and uses it to compute a write offset into a 256-byte buffer. For packet numbers >= 14 the offset exceeds the buffer, and the subsequent memcpy corrupts adjacent struct fields on the heap. Reject packets whose computed offset would overflow the buffer. Confirmed by walking the arithmetic: with PACKET_SIZE 20 the offset is 18n + 2, so packet 14 writes to 272 and packet 15 to 290, past the end of a 256-byte buffer -- 16 and 34 bytes of heap corruption driven by a nibble a paired device chooses. [patola: offset made size_t. As submitted, the bounds check compared an int against sizeof and warned under -Wsign-compare, which is not where you want a signedness question. Restored the two comment lines the patch replaced -- they explain the +2.] Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <sergio@correia.cc> Co-Authored-By: Claude <noreply@anthropic.com>
The CLAMP macro was defined as a no-op (expanding to just x), providing no actual clamping. Replace with a correct implementation. Add a data_len parameter to grab_value/grab_with_size and validate byte_offset against it before reading, preventing out-of-bounds reads from malformed HID reports. Guard the HATSWITCH case against i+1 overflowing the axes array. Recalibrate DS4/DS5 axis parameters to work with the real CLAMP: the old values repurposed clamp_max as a scale factor (e.g. 257), which only worked because CLAMP was a no-op. Normalize fval to [-1, 1] (sticks) or [0, 1] (triggers) and use STICK_PAD_MIN/MAX and TRIGGER_MAX as proper clamp bounds, matching the evdevdrv convention. Set decoder.packet_size in DS4/DS5/BT constructors so the new bounds check has the correct buffer length. Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <scorreia@redhat.com>
The IPC socket accepted arbitrary action strings in Replace: commands,
including shell() which executes commands via subprocess. The Profile:
and Selected: handlers accepted arbitrary filesystem paths, allowing
any socket client to load attacker-controlled files as profiles or
menus containing shell() actions.
Add SafeTalkingActionParser that filters dangerous action types
(shell, profile, restart, exit, turnoff) from Replace: commands,
including in nested/composite actions. The override changes the
default parameter so recursive _parse_action() calls also use the
filtered dict.
Reject Profile: and Selected: names containing '/' and resolve them
through find_profile()/find_menu() which only search known safe
directories.
Note what this filter is and is not. It is defence in depth, not a
security boundary: type() and button() have to keep working for
Replace: to have any purpose, and type('...') plus Enter drives
whatever terminal happens to be focused. The boundary is the socket
being 0600 (see the umask commit); this narrows what a client that
already has that access can do without effort.
[patola: dropped the realpath containment check in find_profile and
find_menu. The basename check already stops traversal completely, and
resolving symlinks additionally rejected a profile that is a symlink
to somewhere else -- how people keep them in a dotfiles repo -- while
only defending against a symlink planted inside the profiles
directory, where an attacker could equally just write a profile full
of shell() actions. Same reasoning in the daemon's two path checks:
abspath instead of realpath, which collapses '..' lexically so
traversal still cannot escape.
Rejecting '.' and '..' explicitly, because basename('..') == '..'
passes the basename check and, for menus, resolves to the directory
itself. The submitted realpath check caught that by accident; the
lexical one has to say so.]
Assisted-by: Claude Opus 4.6
Signed-off-by: Sergio Correia <sergio@correia.cc>
Co-Authored-By: Claude <noreply@anthropic.com>
Replace os.system() in on_sa_restart with subprocess.Popen using a list and find_python(), eliminating shell injection via sys.argv[0] and ensuring the correct Python interpreter is used in AppImage environments. Fix Task.__lt__ which was a no-op (missing return). Add a sequence number as a tiebreaker so equal-timestamp tasks maintain FIFO order and PriorityQueue never falls through to comparing incomparable callback objects. Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <scorreia@redhat.com>
has_haptic_effects() asked whether the connected controller's type is one that can synthesise waveforms. With no controller connected the type is None, which is not in that set, so the Effect row disappeared from the Feedback tab -- reproducible by opening a pad's configuration with the Steam Controller 2 switched off. There are three states, not two: capable, incapable, and nothing attached. An absent controller says nothing about the hardware a profile is written for, and editing a profile with the pad off is ordinary. Treat unknown as capable; a known incapable controller still hides the chooser. Saved effects were never at risk -- update_modifiers skips the effect block when unsupported and _make_action rebuilds from the loaded state, so a Tone opened offline stayed a Tone. There is now a test saying so, because that was the failure worth ruling out. Reported by Patola on a Steam Controller 2. Co-Authored-By: Claude <noreply@anthropic.com>
ReplacedAction.button_press passed mapper as both arguments instead of forwarding the call correctly, causing TypeError on every button press through a Replace'd action. ReportingAction.whole had a misplaced parenthesis that evaluated the comparison inside abs() instead of outside, defeating the Y-axis minimum-difference threshold for event filtering. ReplacedAction.__init__ also bypassed LockedAction.__init__, which meant the held-button-release guard was skipped: if a button was physically held when Replace: was issued, the old action's virtual key stayed stuck down. Add the same release-if-held guard. Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <sergio@correia.cc>
__mul__ passed self.frequency (already scaled by 1000x in the constructor) back through the constructor, which multiplied by 1000 again. This also silently dropped extended effect attributes (tone, sweep, LFO settings). Apply the same pattern as with_position(): construct with a dummy frequency, then copy the pre-scaled frequency and all extended attributes directly. Extract the shared attribute list into a class-level _EFFECT_ATTRS tuple used by both methods. Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <sergio@correia.cc>
RangeOP for both STICK and RSTICK created children that resolved to lpad_x/lpad_y axis names, so modeshift conditions on STICK read left-pad state instead of stick state, and RSTICK conditions never matched the right stick at all. Override children's axis_name to stick_x/stick_y and rstick_x/ rstick_y respectively, and guard _state() against AttributeError for controllers whose state struct lacks the requested axis (every cmp_* already treats None as no-match). [patola: took only the actions.py half of the submitted patch. The DS5 Bluetooth touchpad scaling it also carried is held back: it scales unconditionally, where the USB path it copies only scales inside the touched branch, so a lifted finger would report a pad corner instead of centre -- and it scales a cpad_y that is itself extracted wrongly ((data[37] & 0x0F) << 4 keeps 8 bits of a 12-bit field). That needs the extraction fixed first and a DualSense to test on.] Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <sergio@correia.cc>
The button_map guard allowed values up to 32 (< 33), but shifting a uint32_t by 32 is undefined behavior in C. Change the guard to < 32 so only valid bit positions 0-31 are used. Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <sergio@correia.cc>
The _threaded method and kill() both read and write self.p without synchronization. If kill() checks self.p between _threaded's communicate() return and self.p = None assignment, it may call kill() on None or on a stale Popen. Add a threading.Lock to protect self.p access. Also re-check _killed inside the lock before spawning, closing a narrow TOCTOU between the while-guard and the lock acquisition that could leave an orphaned child process after kill() returns. Assisted-by: Claude Opus 4.6 Signed-off-by: Sergio Correia <sergio@correia.cc>
The "maps to nothing" sentinel was BUTTON_COUNT - 1. That is bit 31, which is RSTICKPRESS -- a real button -- so on a JSON-configured HID controller every input bit the config did not name pressed the right stick, and button_to_bit reported the same thing for a mask it could not resolve. Use BUTTON_UNMAPPED = BUTTON_COUNT instead, which is outside the range the decoder can turn into a button. That value is only safe now that the preceding commit rejects 32 rather than 33: before it, an out-of-range sentinel would have been shifted into a uint32_t by 32 -- undefined behaviour rather than the intended no-op. Found while reviewing Sergio Correia's shift-UB fix, which this builds on. Co-Authored-By: Claude <noreply@anthropic.com>
Two faults that compounded into data loss. save() wrote in place, so anything killing the process mid-write left truncated JSON; reload() then answered the parse failure by calling create(), which overwrites the file with defaults. One interrupted write therefore discarded every setting the user had, with no copy kept. save() now writes a temp file in the same directory, fsyncs it and os.replace()s it in, so a reader sees either the old contents or the new. reload() moves an unreadable config aside to config.json.broken.N before falling back to defaults, numbered so a second failure cannot overwrite the copy taken during the first -- which would be the only surviving record of the settings. A missing file is treated as a first run rather than an error, so it no longer logs a warning or leaves a stray backup. Also: "Minimize to status icon instead closing" -> "instead of closing". Reported by Patola, who lost his settings twice; the fingerprints matched exactly -- minimize_to_status_icon and autokill_daemon sitting at their defaults, news.last_version reverted from 0.4.8 to the default 0.3.12, and osd_color_theme (which has no default) gone entirely. Co-Authored-By: Claude <noreply@anthropic.com>
Navigating an OSD menu with a DualShock 4's left stick was unusable: nudging it scrolled the list far too fast, and holding it fully deflected stopped the selection dead. The d-pad on the same pad was fine, and both were fine on an SC2. One cause, two symptoms. Menus accept STICK, LEFT and DPAD together -- the d-pad arrives as LEFT on a DS4 and as DPAD on an SC2 -- and all of them fed one StickController. _move() emits on every direction change and cancels the repeat timer once the direction reaches zero, so the idle input reporting (0, 0) cancelled the repeat the stick was driving. Movement then happened only on changes: once per event, at the controller's report rate, hence "too fast" -- and nothing at all once the stick was held still and stopped producing events, hence "stalls". set_stick now takes the input the position came from and keeps one entry per source, letting whichever is actually deflected decide. Callers with a single input (launcher, dialog) pass no source and are untouched. Pre-existing: LEFT has always been accepted here. The DPAD added in v0.6.0.7 only affected controllers with a real separate d-pad, which are the ones that behaved correctly. Also makes the packet_size tests from the security series skip rather than fail where libhiddrv is not built. They went in red -- CI has been failing since that merge, because I ran the suite only on a machine that has the extension. Fixes Patola#17 Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Rather reworked version of #100 and its follow-up hard fork, kept rebased against
main.This is mostly for myself, to have an easy shortcut to view what's left to check out, to either implement it or not.
This repository already supports SC2 - it does currently miss some things like additional haptics, actions and some UI changes, that the fork does have.
You can use the
sc2branch by checking it out with git and running./run.shfrom the repo root, if you wish to have those extra missing bits meanwhile.Or you can use the fork - though that is fully diverged from this project at this point, lacking things here and there, with incompatible configurations, so use a separate
~/.config/sccdirectory for it.