diff --git a/README.md b/README.md
index 6479886..b61ed9f 100644
--- a/README.md
+++ b/README.md
@@ -61,6 +61,6 @@ The communication between the Linux client and the Android app are unencrypted U
## Tweaking
-Many aspects of Yoke behavior can be changed easily - ave a look at `yoke/assets/joypad`, `bin/yoke` and `yoke/service.py`.
+Many aspects of Yoke behavior can be changed easily - have a look at `yoke/assets/joypad`, `bin/yoke` and `yoke/service.py`.

diff --git a/devel/app-debug.apk b/devel/app-debug.apk
index 7a1e696..45fc93c 100755
Binary files a/devel/app-debug.apk and b/devel/app-debug.apk differ
diff --git a/yoke/assets/joypad/base.css b/yoke/assets/joypad/base.css
index 66cae88..8df5948 100644
--- a/yoke/assets/joypad/base.css
+++ b/yoke/assets/joypad/base.css
@@ -110,11 +110,24 @@ div {
background-size: 100% 100%;
}
+/* Joysticks */
+.joystick { background-image: url("img/joystick.svg"); background-color: #bbb; }
+.circle {
+ background-color: black;
+ width: 10px;
+ height: 10px;
+ border-radius: 100%;
+}
+
+/* Buttons */
+.button { background-color: #bbb; }
+.pressed { filter: brightness(70%); }
/* seq 1 16 | xargs -I xx echo "#bxx { background-image: url('img/xx.svg'); }" */
-#b1 { background-image: url('img/1.svg'); }
-#b2 { background-image: url('img/2.svg'); }
-#b3 { background-image: url('img/3.svg'); }
-#b4 { background-image: url('img/4.svg'); }
+/* Some edits done by hand */
+#b1 { background-image: url('img/1.svg'); background-color: #00d; }
+#b2 { background-image: url('img/2.svg'); background-color: #e00; }
+#b3 { background-image: url('img/3.svg'); background-color: #dd0; }
+#b4 { background-image: url('img/4.svg'); background-color: #0d0; }
#b5 { background-image: url('img/5.svg'); }
#b6 { background-image: url('img/6.svg'); }
#b7 { background-image: url('img/7.svg'); }
@@ -127,9 +140,27 @@ div {
#b14 { background-image: url('img/14.svg'); }
#b15 { background-image: url('img/15.svg'); }
#b16 { background-image: url('img/16.svg'); }
-
+#bg { background-image: url('img/g.svg'); background-color: #444; }
+#bs { background-image: url('img/s.svg'); background-color: #444; }
+#bm { background-image: url('img/m.svg'); background-color: #444; }
/* printf "du\ndl\ndd\ndr" | xargs -I xx echo "#xx { background-image: url('img/xx.svg'); }" */
#du { background-image: url('img/du.svg'); }
#dl { background-image: url('img/dl.svg'); }
#dd { background-image: url('img/dd.svg'); }
#dr { background-image: url('img/dr.svg'); }
+
+/* Analog buttons */
+#a1 {background-color: #66f;}
+#a2 {background-color: #f33;}
+#a3 {background-color: #ff2;}
+#a4 {background-color: #2f2;}
+
+/* Motion controls */
+.motion {background-color: #ddd;}
+
+/* Pedals */
+.pedal {background-color: #444;}
+
+/* Knobs */
+.knob { }
+.knobcircle {background-color: #888;}
diff --git a/yoke/assets/joypad/base.js b/yoke/assets/joypad/base.js
index b15b1b5..7d510da 100644
--- a/yoke/assets/joypad/base.js
+++ b/yoke/assets/joypad/base.js
@@ -201,16 +201,12 @@ function mnemonics(a, b) {
function truncate(f, id, pattern) {
var truncated = false;
f = f.map(function(val) {
- if (val < 0) { truncated = true; return 0; }
- else if (val > 1) { truncated = true; return 1; }
+ if (val < 0.000001) { truncated = true; return 0.000001; }
+ else if (val > 0.999999) { truncated = true; return 0.999999; }
else { return val; }
});
if (VIBRATE_ON_PAD_BOUNDARY && pattern) {
- if (truncated) {
- queueForVibration(id, pattern);
- } else {
- unqueueForVibration(id);
- }
+ truncated ? queueForVibration(id, pattern) : unqueueForVibration(id);
}
return f;
}
@@ -252,9 +248,16 @@ function Control(type, id, updateStateCallback) {
this._state = 0;
this.kernelEvent = '';
}
+Control.prototype.getBoundingClientRect = function() {
+ this._offset = this.element.getBoundingClientRect();
+ this._offset.semiwidth = this._offset.width / 2;
+ this._offset.semiheight = this._offset.height / 2;
+ this._offset.xCenter = this._offset.x + this._offset.semiwidth;
+ this._offset.yCenter = this._offset.y + this._offset.semiheight;
+};
Control.prototype.onAttached = function() {};
Control.prototype.state = function() {
- return this._state.toString();
+ return Math.floor(256 * this._state);
};
function Joystick(id, updateStateCallback) {
@@ -262,7 +265,6 @@ function Joystick(id, updateStateCallback) {
this._state = [0.5, 0.5];
this.quadrant = -2;
this._locking = (id[0] == 's');
- this._offset = {};
this._circle = document.createElement('div');
this._circle.className = 'circle';
this.element.appendChild(this._circle);
@@ -270,7 +272,6 @@ function Joystick(id, updateStateCallback) {
}
Joystick.prototype = Object.create(Control.prototype);
Joystick.prototype.onAttached = function() {
- this._offset = this.element.getBoundingClientRect();
this._updateCircle();
this.element.addEventListener('touchmove', this.onTouch.bind(this), false);
this.element.addEventListener('touchstart', this.onTouchStart.bind(this), false);
@@ -311,7 +312,12 @@ Joystick.prototype.onTouchEnd = function() {
window.navigator.vibrate(VIBRATION_MILLISECONDS_OUT);
};
Joystick.prototype._updateCircle = function() {
- this._circle.style.transform = 'translate(-50%, -50%) translate(' + (this._offset.x + this._offset.width * this._state[0]) + 'px, ' + (this._offset.y + this._offset.height * this._state[1]) + 'px)';
+ this._circle.style.transform = 'translate(-50%, -50%) translate(' +
+ (this._offset.x + this._offset.width * this._state[0]) + 'px, ' +
+ (this._offset.y + this._offset.height * this._state[1]) + 'px)';
+};
+Joystick.prototype.state = function() {
+ return this._state.map(function(val) {return Math.floor(256 * val);});
};
function Motion(id, updateStateCallback) {
@@ -347,8 +353,8 @@ function Motion(id, updateStateCallback) {
Motion.prototype = Object.create(Control.prototype);
Motion.prototype._normalize = function(f) {
f *= ACCELERATION_CONSTANT;
- if (f < -0.5) { f = -0.5; }
- if (f > 0.5) { f = 0.5; }
+ if (f < -0.499999) { f = -0.499999; }
+ if (f > 0.499999) { f = 0.499999; }
return f + 0.5;
};
Motion.prototype.onAttached = function() {};
@@ -365,18 +371,16 @@ Motion.prototype.onDeviceOrientation = function(ev) {
motionSensor.updateStateCallback();
};
Motion.prototype.state = function() {
- return motionState[this._mask].toString();
+ return Math.floor(256 * motionState[this._mask]);
};
function Pedal(id, updateStateCallback) {
Control.call(this, 'button', id, updateStateCallback);
this._state = 0;
- this._offset = {};
axes += 1;
}
Pedal.prototype = Object.create(Control.prototype);
Pedal.prototype.onAttached = function() {
- this._offset = this.element.getBoundingClientRect();
this.element.addEventListener('touchstart', this.onTouchStart.bind(this), false);
this.element.addEventListener('touchmove', this.onTouchMove.bind(this), false);
this.element.addEventListener('touchend', this.onTouchEnd.bind(this), false);
@@ -415,30 +419,22 @@ function AnalogButton(id, updateStateCallback) {
this.onTouchMoveParticular = function() {};
Control.call(this, 'button', id, updateStateCallback);
this._state = 0;
- this._offset = {};
axes += 1;
}
AnalogButton.prototype = Object.create(Control.prototype);
AnalogButton.prototype.onAttached = function() {
- this._offset = this.element.getBoundingClientRect();
this.element.addEventListener('touchstart', this.onTouchStart.bind(this), false);
this.element.addEventListener('touchmove', this.onTouchMove.bind(this), false);
this.element.addEventListener('touchend', this.onTouchEnd.bind(this), false);
if (this._offset.width > this._offset.height) {
- this._offset.width /= 2;
- this._offset.x += this._offset.width;
this.onTouchMoveParticular = function(ev) {
var pos = ev.targetTouches[0];
- return truncate([1 - Math.abs(this._offset.x - pos.pageX) / this._offset.width]);
-
+ return truncate([1 - Math.abs(this._offset.xCenter - pos.pageX) / this._offset.semiwidth]);
};
} else {
- this._offset.height /= 2;
- this._offset.y += this._offset.height;
this.onTouchMoveParticular = function(ev) {
var pos = ev.targetTouches[0];
- return truncate([1 - Math.abs(this._offset.y - pos.pageY) / this._offset.height]);
-
+ return truncate([1 - Math.abs(this._offset.yCenter - pos.pageY) / this._offset.semiheight]);
};
}
};
@@ -470,7 +466,6 @@ AnalogButton.prototype.onTouchEnd = function() {
function Knob(id, updateStateCallback) {
Control.call(this, 'knob', id, updateStateCallback);
this._state = 0;
- this._offset = {};
this._knobcircle = document.createElement('div');
this._knobcircle.className = 'knobcircle';
this.element.appendChild(this._knobcircle);
@@ -481,23 +476,20 @@ function Knob(id, updateStateCallback) {
}
Knob.prototype = Object.create(Control.prototype);
Knob.prototype.onAttached = function() {
- // First approximation to the knob coordinates.
- this._offset = this.element.getBoundingClientRect();
// Centering the knob within the boundary.
- var minDimension = Math.min(this._offset.width, this._offset.height);
- if (minDimension == this._offset.width) {
- this._knobcircle.style.top = this._offset.y + (this._offset.height - this._offset.width) / 2 + 'px';
+ if (this._offset.width < this._offset.height) {
+ this._offset.y += this._offset.semiheight - this._offset.semiwidth;
this._offset.height = this._offset.width;
+ this._offset.semiheight = this._offset.semiwidth;
} else {
- this._knobcircle.style.left = this._offset.x + (this._offset.width - this._offset.height) / 2 + 'px';
+ this._offset.x += this._offset.semiwidth - this._offset.semiheight;
this._offset.width = this._offset.height;
+ this._offset.semiwidth = this._offset.semiheight;
}
+ this._knobcircle.style.top = this._offset.y + 'px';
+ this._knobcircle.style.left = this._offset.x + 'px';
this._knobcircle.style.height = this._offset.width + 'px';
this._knobcircle.style.width = this._offset.height + 'px';
- // Calculating the exact center.
- this._offset = this._knobcircle.getBoundingClientRect();
- this._offset.x += this._offset.width / 2;
- this._offset.y += this._offset.height / 2;
this._updateCircles();
this.quadrant = 0;
this.element.addEventListener('touchmove', this.onTouch.bind(this), false);
@@ -506,7 +498,7 @@ Knob.prototype.onAttached = function() {
};
Knob.prototype.onTouch = function(ev) {
var pos = ev.targetTouches[0];
- this._state = Math.atan2(pos.pageY - this._offset.y, pos.pageX - this._offset.x) / 2 / Math.PI + 0.5;
+ this._state = Math.atan2(pos.pageY - this._offset.yCenter, pos.pageX - this._offset.xCenter) / 2 / Math.PI + 0.5;
this.updateStateCallback();
var currentQuadrant = Math.floor(this._state * 16);
if (VIBRATE_ON_QUADRANT_BOUNDARY && this.quadrant != currentQuadrant) {
@@ -531,7 +523,7 @@ Knob.prototype._updateCircles = function() {
function Button(id, updateStateCallback) {
Control.call(this, 'button', id, updateStateCallback);
- this._state = 0;
+ this._state = false;
buttons += 1;
}
Button.prototype = Object.create(Control.prototype);
@@ -541,17 +533,20 @@ Button.prototype.onAttached = function() {
};
Button.prototype.onTouchStart = function(ev) {
ev.preventDefault(); // Android Webview delays the vibration without this.
- this._state = 1;
+ this._state = true;
this.updateStateCallback();
this.element.classList.add('pressed');
window.navigator.vibrate(VIBRATION_MILLISECONDS_IN);
};
Button.prototype.onTouchEnd = function() {
- this._state = 0;
+ this._state = false;
this.updateStateCallback();
this.element.classList.remove('pressed');
window.navigator.vibrate(VIBRATION_MILLISECONDS_OUT);
};
+Button.prototype.state = function() {
+ return (this._state ? 1 : 0);
+};
function Dummy(id, updateStateCallback) {
Control.call(this, 'dummy', 'dum', updateStateCallback);
@@ -583,6 +578,7 @@ function Joypad() {
}, this);
this._controls.forEach(function(control) {
this.element.appendChild(control.element);
+ control.getBoundingClientRect();
control.onAttached();
}, this);
if (axes == 0 && buttons == 0) {
@@ -614,11 +610,6 @@ function Joypad() {
Joypad.prototype.updateState = function() {
var state = this._controls.map(function(control) { return control.state(); }).join(',');
- // We are reducing float precision to avoid getting UDP messages cut in half.
- // We are re-splitting the string since some control.state() above return strings
- // (e.g. because [0.5, 0.5].toString() == '0.5,0.5')
- state = state.split(',').map(function(x) { return x.substr(0, 6); }).join(',');
-
// Within the Yoke webview, sends the joypad state.
// Outside the Yoke webview, window.Yoke.update_vals() is redefined to have no effect.
// This prevents JavaScript exceptions, and wastes less CPU time when in Yoke:
diff --git a/yoke/assets/joypad/gamepad.css b/yoke/assets/joypad/gamepad.css
index 5218e0e..c12bc61 100644
--- a/yoke/assets/joypad/gamepad.css
+++ b/yoke/assets/joypad/gamepad.css
@@ -32,7 +32,8 @@
* g for SELECT button,
* m for the branded button (HOME or equivalent),
* 1, 2, 3, 4, 5, 6, 7, 8, 9, 10... for the rest.
- * d_ for D-PAD, where _ is one of these letters:
+ * a_ for analog button/trigger, where _ is a number;
+ * d_ for D-pad, where _ is one of these letters:
* u, for the key UP,
* d, for the key DOWN,
* l, for the key LEFT,
@@ -40,26 +41,3 @@
* dbg for debug messages;
* a period for empty space. */
}
-
-.joystick { background-image: url("img/joystick.svg"); background-color: #bbb; }
-.circle {
- background-color: black;
- width: 10px;
- height: 10px;
- border-radius: 100%;
-}
-
-.motion {background-color: #ddd;}
-
-.pedal {background-color: #444;}
-
-.knob { }
-.knobcircle {background-color: #888;}
-
-.button { background-color: #bbb; }
-.pressed { filter: brightness(70%); }
-#b1 {background-color: #22e;}
-#b2 {background-color: #e00;}
-#b3 {background-color: #dd0;}
-#b4 {background-color: #0d0;}
-#bg, #bs, #bm {background-color: #444;}
diff --git a/yoke/assets/joypad/img/g.svg b/yoke/assets/joypad/img/g.svg
new file mode 100644
index 0000000..b641c1e
--- /dev/null
+++ b/yoke/assets/joypad/img/g.svg
@@ -0,0 +1,4 @@
+
diff --git a/yoke/assets/joypad/img/m.svg b/yoke/assets/joypad/img/m.svg
new file mode 100644
index 0000000..23a4f7e
--- /dev/null
+++ b/yoke/assets/joypad/img/m.svg
@@ -0,0 +1,4 @@
+
diff --git a/yoke/assets/joypad/img/s.svg b/yoke/assets/joypad/img/s.svg
new file mode 100644
index 0000000..7d6904b
--- /dev/null
+++ b/yoke/assets/joypad/img/s.svg
@@ -0,0 +1,4 @@
+
diff --git a/yoke/assets/joypad/racing.css b/yoke/assets/joypad/racing.css
index 8c32be0..3769916 100644
--- a/yoke/assets/joypad/racing.css
+++ b/yoke/assets/joypad/racing.css
@@ -32,7 +32,8 @@
* g for SELECT button,
* m for the branded button (HOME or equivalent),
* 1, 2, 3, 4, 5, 6, 7, 8, 9, 10... for the rest.
- * d_ for D-PAD, where _ is one of these letters:
+ * a_ for analog button/trigger, where _ is a number;
+ * d_ for D-pad, where _ is one of these letters:
* u, for the key UP,
* d, for the key DOWN,
* l, for the key LEFT,
@@ -40,26 +41,3 @@
* dbg for debug messages;
* a period for empty space. */
}
-
-.joystick { background-image: url("img/joystick.svg"); background-color: #bbb; }
-.circle {
- background-color: black;
- width: 10px;
- height: 10px;
- border-radius: 100%;
-}
-
-.motion {background-color: #ddd;}
-
-.pedal {background-color: #444;}
-
-.knob { }
-.knobcircle {background-color: #888;}
-
-.button { background-color: #bbb; }
-.pressed { filter: brightness(70%); }
-#b1 {background-color: #22e;}
-#b2 {background-color: #e00;}
-#b3 {background-color: #dd0;}
-#b4 {background-color: #0d0;}
-#bg, #bs, #bm {background-color: #444;}
diff --git a/yoke/assets/joypad/testing.css b/yoke/assets/joypad/testing.css
index e5b93c3..3c0e623 100644
--- a/yoke/assets/joypad/testing.css
+++ b/yoke/assets/joypad/testing.css
@@ -33,7 +33,7 @@
* m for the branded button (HOME or equivalent),
* 1, 2, 3, 4, 5, 6, 7, 8, 9, 10... for the rest;
* a_ for analog button/trigger, where _ is a number;
- * d_ for D-PAD, where _ is one of these letters:
+ * d_ for D-pad, where _ is one of these letters:
* u, for the key UP,
* d, for the key DOWN,
* l, for the key LEFT,
@@ -41,31 +41,3 @@
* dbg for debug messages;
* a period for empty space. */
}
-
-.joystick { background-image: url("img/joystick.svg"); background-color: #bbb; }
-.circle {
- background-color: black;
- width: 10px;
- height: 10px;
- border-radius: 100%;
-}
-
-.motion {background-color: #ddd;}
-
-.pedal {background-color: #444;}
-
-.knob { }
-.knobcircle {background-color: #888;}
-
-.button { background-color: #bbb; }
-.pressed { filter: brightness(70%); }
-#b1 {background-color: #22e;}
-#b2 {background-color: #e00;}
-#b3 {background-color: #dd0;}
-#b4 {background-color: #0d0;}
-#bg, #bs, #bm {background-color: #444;}
-
-#a1 {background-color: #66f;}
-#a2 {background-color: #f33;}
-#a3 {background-color: #ff2;}
-#a4 {background-color: #2f2;}
diff --git a/yoke/service.py b/yoke/service.py
index 2b27c01..f001496 100644
--- a/yoke/service.py
+++ b/yoke/service.py
@@ -52,8 +52,6 @@ def get_ip_address():
)
-
-
ABS_EVENTS = [getattr(EVENTS, n) for n in dir(EVENTS) if n.startswith("ABS_")]
class Device:
@@ -77,8 +75,6 @@ def __init__(self, id=1, name="Yoke", events=GAMEPAD_EVENTS):
def emit(self, d, v):
if d not in self.events:
raise AttributeError("Event {} has not been registered.".format(d))
- if d in ABS_EVENTS:
- v = (v+1)/2 * 255
self.device.emit(d, int(v), syn=False)
def flush(self):
@@ -109,7 +105,7 @@ def emit(self, d, v):
if d in range(1, 8+1):
self.device.set_button(d, v)
else:
- self.device.set_axis(d, int((v+1)/2 * 32768))
+ self.device.set_axis(d, int(v * 32767 / 255))
def flush(self):
pass
def close(self):
@@ -175,23 +171,11 @@ def __init__(self, dev, iface='auto', port=0, client_path=DEFAULT_CLIENT_PATH):
self.client_path = client_path
def make_events(self, values):
- """returns a (event_code, value) tuple for each value in values
- values are in (-1, 1) and should be returned in (-1, 1)
- """
raise NotImplementedError()
def preprocess(self, message):
- _, *v, _ = message.split(b',') # first and last value is nothing
- _, *v = v # first real value (from accelerometer) is not important yet
- _, _, *v = v # ignore 2 values from accelerometer in Android code
- # TODO: remove when removing corresponding Android code
- v = [float(m) for m in v]
- v = ( # TODO: normalize from [0, 1] to [-1, 1] on JS side
- v[0] * 2 - 1,
- v[1] * 2 - 1,
- v[2] * 2 - 1,
- v[3] * 2 - 1,
- ) + tuple(v[4:])
+ v = message.split(b',')
+ v = tuple([int(m) for m in v])
if len(v) < len(GAMEPAD_EVENTS):
# Before reducing float precision, sometimes UDP messages were getting cut in half.
# Keeping the code just in case.