1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
use crate::error::{Error, Result};
use crate::protocol::FrameType;
use crate::protocol::command::lock_payload;
use crate::protocol::opcode::{
LOCK_AXIS_WHEEL, LOCK_AXIS_X, LOCK_AXIS_Y, LOCK_CLS_AXIS, LOCK_CLS_BTN, LOCK_CLS_KEY,
LOCK_CLS_MEDIA, LOCK_ID_ALL, LOCK_SCALE_BLOCK, LOCK_SCALE_PASS,
};
use crate::types::{Axis, Blanket, Direction, LockTarget};
use super::Device;
impl Device {
fn send_lock(&self, class: u8, id: u16, direction: Direction, scale: u8) -> Result<()> {
let dir = lock_direction(class, direction)?.as_u8();
// Recorded before the write so a reconnect racing it still replays the lock, and rolled back
// when the frame never went out: a desired state the box was never told about holds the
// keepalive open for a lock nothing is applying.
let undo = self
.link
.desired()
.lock()
.apply_lock((class, id, dir), scale);
let sent = self
.link
.send(FrameType::Lock, &lock_payload(class, id, dir, scale));
if sent.is_err() {
self.link.desired().lock().restore_lock(undo);
}
sent
}
/// `LOCK` weighs physical input on a target while host injection still drives it; reverts on
/// control-PC silence.
///
/// `scale` is the percent of the physical value the box keeps on that direction:
/// [`LOCK_SCALE_BLOCK`] blocks it, [`LOCK_SCALE_PASS`] passes it untouched, and above that
/// amplifies, to [`LOCK_SCALE_MAX`](crate::LOCK_SCALE_MAX) = 2.55x.
/// [`lock`](Self::lock) and [`unlock`](Self::unlock) are the two ends of this one number.
///
/// A delta picks up at most two scales, its absolute direction's and its relative direction's, and
/// they multiply: a `Negative` of 50 with an `Against` of 40 lands leftward-while-injecting-right at
/// 20%. A block anywhere therefore wins outright.
///
/// [`Direction::Both`] addresses the whole target, writing the scale to the two absolute directions
/// and a full pass to the two relative ones. Writing it to all four would square it, so a plain
/// `Both` of 50 would mean 50% with no bearing live and 25% with one. Name a relative direction to
/// weigh it.
///
/// [`Direction::With`] and [`Direction::Against`] are measured against the bearing and do nothing
/// until one is live; see [`set_bearing`](Self::set_bearing). Only an axis has a bearing, so a
/// relative direction on a button, key or media usage is
/// [`Error::RelativeDirection`](crate::Error::RelativeDirection) rather than a frame the box
/// discards. A momentary usage carries one bit, so any scale below a full pass locks it and there
/// is nothing in between; a scale at or above a full pass on one is an unlock.
///
/// A media usage has no edges (it is suppressed whole), so an edge direction on one is sent as
/// [`Direction::Both`], which is what `RESP(LOCKS)` reports it as.
///
/// ```no_run
/// # use medius::{Axis, Device, Direction, Result};
/// # fn main() -> Result<()> {
/// let device = Device::find()?;
/// device.scale(Axis::X, Direction::Against, 40)?; // 40% of physical motion opposing the injection
/// device.scale(Axis::X, Direction::With, 130)?; // 130% of physical helping it
/// # Ok(()) }
/// ```
pub fn scale(
&self,
target: impl Into<LockTarget>,
direction: Direction,
scale: u8,
) -> Result<()> {
let (class, id) = target_class_id(target.into());
self.send_lock(class, id, direction, scale)
}
/// `LOCK` blocks physical input on a target while host injection still drives it; reverts on
/// control-PC silence. The same as [`scale`](Self::scale) at [`LOCK_SCALE_BLOCK`].
pub fn lock(&self, target: impl Into<LockTarget>, direction: Direction) -> Result<()> {
self.scale(target, direction, LOCK_SCALE_BLOCK)
}
/// Release a lock on the given target/direction, back to passing untouched. The same as
/// [`scale`](Self::scale) at [`LOCK_SCALE_PASS`].
///
/// [`Direction::Both`] clears every direction of the target, the relative pair included, so an
/// unlock never walks away from a bearing scale that would go on weighing unseen. It is total in a
/// way a `Both` at any other scale is not: only a full pass reaches the relative pair.
pub fn unlock(&self, target: impl Into<LockTarget>, direction: Direction) -> Result<()> {
self.scale(target, direction, LOCK_SCALE_PASS)
}
/// `LOCK` a relative axis by sign; convenience for `lock(axis, direction)`.
pub fn lock_axis(&self, axis: Axis, direction: Direction) -> Result<()> {
self.lock(axis, direction)
}
/// Release an axis lock.
pub fn unlock_axis(&self, axis: Axis, direction: Direction) -> Result<()> {
self.unlock(axis, direction)
}
/// Weigh a relative axis by sign; convenience for `scale(axis, direction, scale)`.
pub fn scale_axis(&self, axis: Axis, direction: Direction, scale: u8) -> Result<()> {
self.scale(axis, direction, scale)
}
/// `LOCK` a whole [`Blanket`] group (X and Y, the wheel, or every button / key / media usage).
///
/// [`Blanket::Keys`] honours the direction: `Positive` blocks press edges only, `Negative`
/// release edges only.
pub fn lock_all(&self, what: Blanket, direction: Direction) -> Result<()> {
self.blanket(what, direction, LOCK_SCALE_BLOCK)
}
/// Release a blanket lock.
pub fn unlock_all(&self, what: Blanket, direction: Direction) -> Result<()> {
self.blanket(what, direction, LOCK_SCALE_PASS)
}
/// Weigh a whole [`Blanket`] group; see [`scale`](Self::scale) for what the number means.
pub fn scale_all(&self, what: Blanket, direction: Direction, scale: u8) -> Result<()> {
self.blanket(what, direction, scale)
}
fn blanket(&self, what: Blanket, direction: Direction, scale: u8) -> Result<()> {
match what {
Blanket::Aim => {
self.send_lock(LOCK_CLS_AXIS, LOCK_AXIS_X, direction, scale)?;
self.send_lock(LOCK_CLS_AXIS, LOCK_AXIS_Y, direction, scale)
}
Blanket::Wheel => self.send_lock(LOCK_CLS_AXIS, LOCK_AXIS_WHEEL, direction, scale),
Blanket::Buttons => self.send_lock(LOCK_CLS_BTN, LOCK_ID_ALL, direction, scale),
Blanket::Keys => self.send_lock(LOCK_CLS_KEY, LOCK_ID_ALL, direction, scale),
Blanket::Media => self.send_lock(LOCK_CLS_MEDIA, LOCK_ID_ALL, direction, scale),
}
}
}
fn target_class_id(target: LockTarget) -> (u8, u16) {
match target {
LockTarget::Axis(a) => (LOCK_CLS_AXIS, a.as_u16()),
LockTarget::Usage(u) => u.class_id(),
}
}
// Only an axis has a bearing to be with or against; the box drops a relative direction on any other
// class with no reply. A media usage has no edges either (it is suppressed whole, and RESP(LOCKS)
// reports every media lock as Both), so an edge there becomes the Both the box will report back.
fn lock_direction(class: u8, direction: Direction) -> Result<Direction> {
if class == LOCK_CLS_AXIS {
return Ok(direction);
}
if direction.is_relative() {
return Err(Error::RelativeDirection {
direction,
what: match class {
LOCK_CLS_BTN => "button lock",
LOCK_CLS_KEY => "key lock",
_ => "media lock",
},
});
}
Ok(if class == LOCK_CLS_MEDIA {
Direction::Both
} else {
direction
})
}