#include "movement_maker.h"
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <util/BitUtils.h>
#ifdef TRACE_MOVEMENT_MAKER
# include <String.h>
# include <headers/private/shared/FunctionTracer.h>
static int32 sFunctionDepth = -1;
# define CALLED(x...) FunctionTracer _ft(debug_printf, this, __PRETTY_FUNCTION__, sFunctionDepth)
# define TRACE(x...) do { BString _to; \
_to.Append(' ', (sFunctionDepth + 1) * 2); \
char _extra[1024]; \
sprintf(_extra, x); \
debug_printf("%p -> %s%s", this, _to.String(), _extra); \
} while (0)
# define LOG_EVENT(text...) do {} while (0)
# define LOG_ERROR(text...) TRACE(text)
#else
# define TRACE(x...)
# define CALLED(x...)
# define LOG_ERROR(x...) do { debug_printf(x); } while(0)
# define LOG_EVENT(x...)
#endif
#define SYN_WIDTH (4100)
#define SYN_HEIGHT (3140)
static int32
make_small(float value)
{
return (int32)truncf(value);
}
static inline bool
two_fingers(const touchpad_movement* event) {
return count_set_bits(event->fingers) == 2
|| event->fingerWidth == 0;
}
static inline bool
two_or_more_fingers(const touchpad_movement* event) {
return count_set_bits(event->fingers) >= 2
|| event->fingerWidth == 0 || event->fingerWidth == 1;
}
static inline bool
three_fingers(const touchpad_movement* event) {
return count_set_bits(event->fingers) == 3
|| event->fingerWidth == 1;
}
static inline bool
three_or_more_fingers(const touchpad_movement* event) {
return count_set_bits(event->fingers) > 2
|| event->fingerWidth == 1;
}
void
MovementMaker::SetSettings(const touchpad_settings& settings)
{
CALLED();
fSettings = settings;
}
void
MovementMaker::SetSpecs(const touchpad_specs& specs)
{
CALLED();
fSpecs = specs;
fAreaWidth = fSpecs.areaEndX - fSpecs.areaStartX;
fAreaHeight = fSpecs.areaEndY - fSpecs.areaStartY;
fSpeed = SYN_WIDTH / fAreaWidth;
fSmallMovement = 3 / fSpeed;
}
void
MovementMaker::StartNewMovment()
{
CALLED();
if (fSettings.scroll_xstepsize <= 0)
fSettings.scroll_xstepsize = 1;
if (fSettings.scroll_ystepsize <= 0)
fSettings.scroll_ystepsize = 1;
fMovementMakerStarted = true;
scrolling_x = 0;
scrolling_y = 0;
}
void
MovementMaker::GetMovement(uint32 posX, uint32 posY)
{
CALLED();
_GetRawMovement(posX, posY);
}
void
MovementMaker::GetScrolling(uint32 posX, uint32 posY, bool reverse)
{
CALLED();
int32 stepsX = 0, stepsY = 0;
int32 directionMultiplier = reverse ? -1 : 1;
_GetRawMovement(posX, posY);
_ComputeAcceleration(fSettings.scroll_acceleration);
if (fSettings.scroll_xstepsize > 0) {
scrolling_x += directionMultiplier * xDelta;
stepsX = make_small(scrolling_x / fSettings.scroll_xstepsize);
scrolling_x -= stepsX * fSettings.scroll_xstepsize;
xDelta = stepsX;
} else {
scrolling_x = 0;
xDelta = 0;
}
if (fSettings.scroll_ystepsize > 0) {
scrolling_y += directionMultiplier * yDelta;
stepsY = make_small(scrolling_y / fSettings.scroll_ystepsize);
scrolling_y -= stepsY * fSettings.scroll_ystepsize;
yDelta = -1 * stepsY;
} else {
scrolling_y = 0;
yDelta = 0;
}
}
void
MovementMaker::_GetRawMovement(uint32 posX, uint32 posY)
{
CALLED();
posX = posX * SYN_WIDTH / fAreaWidth;
posY = posY * SYN_HEIGHT / fAreaHeight;
const float acceleration = 0.8;
const float translation = 12.0;
int diff;
if (fMovementMakerStarted) {
fMovementMakerStarted = false;
fPreviousX = posX;
fPreviousY = posY;
}
diff = posX - fPreviousX;
if ((diff > -fSmallMovement && diff < -1)
|| (diff > 1 && diff < fSmallMovement)) {
diff /= 2;
}
if (diff == 0)
fDeltaSumX = 0;
else
fDeltaSumX += diff;
diff = posY - fPreviousY;
if ((diff > -fSmallMovement && diff < -1)
|| (diff > 1 && diff < fSmallMovement)) {
diff /= 2;
}
if (diff == 0)
fDeltaSumY = 0;
else
fDeltaSumY += diff;
fPreviousX = posX;
fPreviousY = posY;
xDelta = fDeltaSumX / translation;
yDelta = fDeltaSumY / translation;
if (xDelta > 1.0) {
fDeltaSumX = 0.0;
xDelta = 1.0 + (xDelta - 1.0) * acceleration;
} else if (xDelta < -1.0) {
fDeltaSumX = 0.0;
xDelta = -1.0 + (xDelta + 1.0) * acceleration;
}
if (yDelta > 1.0) {
fDeltaSumY = 0.0;
yDelta = 1.0 + (yDelta - 1.0) * acceleration;
} else if (yDelta < -1.0) {
fDeltaSumY = 0.0;
yDelta = -1.0 + (yDelta + 1.0) * acceleration;
}
xDelta = make_small(xDelta);
yDelta = make_small(yDelta);
}
void
MovementMaker::_ComputeAcceleration(int8 accel_factor)
{
CALLED();
float acceleration = 1;
if (accel_factor != 0) {
acceleration = 1 + sqrtf(xDelta * xDelta
+ yDelta * yDelta) * accel_factor / 50.0;
}
xDelta = make_small(xDelta * acceleration);
yDelta = make_small(yDelta * acceleration);
}
#define fTapTimeOUT 200000
TouchpadMovement::TouchpadMovement()
{
CALLED();
fMovementStarted = false;
fScrollingStarted = false;
fTapStarted = false;
fValidEdgeMotion = false;
fDoubleClick = false;
}
TouchpadMovement::~TouchpadMovement() {
CALLED();
}
status_t
TouchpadMovement::EventToMovement(const touchpad_movement* _event, mouse_movement* movement,
bigtime_t& repeatTimeout)
{
CALLED();
if (!movement)
return B_ERROR;
TRACE("TM_EVENT: b:0x%" B_PRIx8 " nf:%" B_PRId8 " f:0x%" B_PRIx8
" x:%" B_PRIu32 " y:%" B_PRIu32 " p:%" B_PRIu8 " w:%" B_PRIu8 "\n",
_event->buttons,
count_set_bits(_event->fingers),
_event->fingers,
_event->xPosition,
_event->yPosition,
_event->zPressure,
_event->fingerWidth
);
TRACE("TM_STATUS: b:0x%" B_PRIx8 " %c%c%c%c%c%c"
" dx:%" B_PRId32 " dy:%" B_PRId32
" tcks:%" B_PRId32 " cks:%" B_PRId32 "\n",
fButtonsState,
fMovementStarted ? 'M' : 'm',
fScrollingStarted ? 'S' : 's',
fTapStarted ? 'T' : 't',
fTapdragStarted ? 'D' : 'd',
fValidEdgeMotion ? 'E' : 'e',
fDoubleClick ? 'C' : 'c',
fTapDeltaX,
fTapDeltaY,
fTapClicks,
fClickCount
);
movement->xdelta = 0;
movement->ydelta = 0;
movement->buttons = 0;
movement->wheel_ydelta = 0;
movement->wheel_xdelta = 0;
movement->modifiers = 0;
movement->clicks = 0;
movement->timestamp = system_time();
touchpad_movement event2 = *_event;
if (!_ClickFingerButtonEmulator(&event2))
_SoftwareButtonAreas(&event2);
const touchpad_movement* event = &event2;
if ((movement->timestamp - fTapTime) > fTapTimeOUT) {
if (fTapStarted)
TRACE("TouchpadMovement: tap gesture timed out\n");
fTapStarted = false;
if (!fDoubleClick
|| (movement->timestamp - fTapTime) > 2 * fTapTimeOUT) {
fTapClicks = 0;
}
}
if (event->buttons || two_or_more_fingers(event)) {
fTapClicks = 0;
fTapdragStarted = false;
fTapStarted = false;
if ((fSettings.edge_motion
& (B_EDGE_MOTION_ON_BUTTON_CLICK_MOVE | B_EDGE_MOTION_ON_BUTTON_CLICK_DRAG)) != 0)
fValidEdgeMotion = false;
}
if (event->zPressure >= fSpecs.minPressure
&& event->zPressure < fSpecs.maxPressure
&& ((event->fingerWidth >= 4 && event->fingerWidth <= 7)
|| two_or_more_fingers(event))
&& (event->xPosition != 0 || event->yPosition != 0)) {
if (!_CheckScrollingToMovement(event, movement))
_MoveToMovement(event, movement);
} else
_NoTouchToMovement(event, movement);
if (fTapdragStarted || fValidEdgeMotion) {
repeatTimeout = 1000 * 50;
} else
repeatTimeout = B_INFINITE_TIMEOUT;
return B_OK;
}
bool
TouchpadMovement::_ClickFingerButtonEmulator(touchpad_movement *event) {
CALLED();
if (event->buttons != 0) {
if (fSettings.finger_click && two_fingers(event)) {
event->buttons = 2;
return true;
} else if (fSettings.finger_click && three_fingers(event)) {
event->buttons = 4;
return true;
}
}
return false;
}
void
TouchpadMovement::_SoftwareButtonAreas(touchpad_movement *event) {
CALLED();
if (event->buttons != 0) {
if (fSettings.software_button_areas) {
if (fButtonsState == 0) {
uint32 evaluatePositionX = (event->xPosition == 0) ? fPreviousX : event->xPosition;
uint32 evaluatePositionY = (event->yPosition == 0) ? fPreviousY : event->yPosition;
if (evaluatePositionY > 0 && evaluatePositionY < (uint32) fSpecs.areaEndY / 5) {
#if 0
if (evaluatePositionX > (uint32) fSpecs.areaEndX / 2) {
event->buttons = 2;
} else {
event->buttons = 1;
}
#else
if (evaluatePositionX > (uint32) fSpecs.areaEndX / 12 * 7) {
event->buttons = 2;
} else if (evaluatePositionX > (uint32) fSpecs.areaEndX / 12 * 5) {
event->buttons = 4;
} else {
event->buttons = 1;
}
#endif
}
} else {
event->buttons = fButtonsState;
}
}
}
}
const int32 kEdgeMotionSpeed = 200;
bool
TouchpadMovement::_EdgeMotion(const touchpad_movement *event, mouse_movement *movement,
bool validStart)
{
CALLED();
float xdelta = 0;
float ydelta = 0;
bigtime_t time = system_time();
if (fLastEdgeMotion != 0) {
xdelta = fRestEdgeMotion + kEdgeMotionSpeed *
float(time - fLastEdgeMotion) / (1000 * 1000);
fRestEdgeMotion = xdelta - int32(xdelta);
ydelta = xdelta;
} else {
fRestEdgeMotion = 0;
}
bool inXEdge = false;
bool inYEdge = false;
if (int32(event->xPosition) < fSpecs.areaStartX + fSpecs.edgeMotionWidth) {
inXEdge = true;
xdelta *= -1;
} else if (event->xPosition > uint16(
fSpecs.areaEndX - fSpecs.edgeMotionWidth)) {
inXEdge = true;
}
if (int32(event->yPosition) < fSpecs.areaStartY + fSpecs.edgeMotionWidth) {
inYEdge = true;
ydelta *= -1;
} else if (event->yPosition > uint16(
fSpecs.areaEndY - fSpecs.edgeMotionWidth)) {
inYEdge = true;
}
if (inXEdge && validStart)
movement->xdelta = make_small(xdelta);
if (inYEdge && validStart)
movement->ydelta = make_small(ydelta);
if (!inXEdge && !inYEdge)
fLastEdgeMotion = 0;
else
fLastEdgeMotion = time;
if ((inXEdge || inYEdge) && !validStart)
return false;
return true;
}
void
TouchpadMovement::_UpdateButtons(mouse_movement *movement)
{
CALLED();
if (movement->buttons != 0 && fButtonsState == 0) {
if (fClickLastTime + click_speed > movement->timestamp)
fClickCount++;
else
fClickCount = 1;
fClickLastTime = movement->timestamp;
}
if (movement->buttons != 0)
movement->clicks = fClickCount;
fButtonsState = movement->buttons;
}
void
TouchpadMovement::_NoTouchToMovement(const touchpad_movement *event,
mouse_movement *movement)
{
CALLED();
uint32 buttons = event->buttons;
if (fMovementStarted)
TRACE("TouchpadMovement: no touch event\n");
fScrollingStarted = false;
fMovementStarted = false;
fLastEdgeMotion = 0;
if (fTapdragStarted
&& (movement->timestamp - fTapTime) < fTapTimeOUT) {
buttons = kLeftButton;
}
if ((movement->timestamp - fTapTime) > fTapTimeOUT) {
if (fTapdragStarted)
TRACE("TouchpadMovement: tap drag gesture timed out\n");
fTapdragStarted = false;
fValidEdgeMotion = false;
}
if (abs(fTapDeltaX) > 15 || abs(fTapDeltaY) > 15) {
fTapStarted = false;
fTapClicks = 0;
}
if (fTapStarted || fDoubleClick) {
TRACE("TouchpadMovement: tap gesture\n");
fTapClicks++;
if (fTapClicks > 1) {
TRACE("TouchpadMovement: empty click\n");
buttons = kNoButton;
fTapClicks = 0;
fDoubleClick = true;
} else {
buttons = kLeftButton;
fTapStarted = false;
fTapdragStarted = true;
fDoubleClick = false;
}
}
movement->buttons = buttons;
_UpdateButtons(movement);
}
void
TouchpadMovement::_MoveToMovement(const touchpad_movement *event, mouse_movement *movement)
{
CALLED();
bool isStartOfMovement = false;
float pressure = 0;
TRACE("TouchpadMovement: movement event\n");
if (!fMovementStarted) {
isStartOfMovement = true;
fMovementStarted = true;
StartNewMovment();
}
GetMovement(event->xPosition, event->yPosition);
movement->xdelta = make_small(xDelta);
movement->ydelta = make_small(yDelta);
fTapDeltaX += make_small(xDelta);
fTapDeltaY += make_small(yDelta);
if (fTapdragStarted) {
movement->buttons = kLeftButton;
movement->clicks = 0;
if (fSettings.edge_motion & B_EDGE_MOTION_ON_TAP_DRAG
|| (event->buttons && (fSettings.edge_motion & B_EDGE_MOTION_ON_BUTTON_CLICK_DRAG)))
fValidEdgeMotion = _EdgeMotion(event, movement, fValidEdgeMotion);
TRACE("TouchpadMovement: tap drag\n");
} else {
if (fSettings.edge_motion & B_EDGE_MOTION_ON_MOVE
|| (event->buttons && (fSettings.edge_motion & B_EDGE_MOTION_ON_BUTTON_CLICK_MOVE)))
fValidEdgeMotion = _EdgeMotion(event, movement, fValidEdgeMotion);
TRACE("TouchpadMovement: movement set buttons\n");
movement->buttons = event->buttons;
}
pressure = 20 * (event->zPressure - fSpecs.minPressure)
/ (fSpecs.realMaxPressure - fSpecs.minPressure);
if (!fTapStarted
&& isStartOfMovement
&& fSettings.tapgesture_sensibility > 0.
&& fSettings.tapgesture_sensibility > (20 - pressure)) {
TRACE("TouchpadMovement: tap started\n");
fTapStarted = true;
fTapTime = system_time();
fTapDeltaX = 0;
fTapDeltaY = 0;
}
_UpdateButtons(movement);
}
bool
TouchpadMovement::_CheckScrollingToMovement(const touchpad_movement *event,
mouse_movement *movement)
{
CALLED();
bool isSideScrollingV = false;
bool isSideScrollingH = false;
if (event->buttons != 0)
return false;
if ((fSpecs.areaEndX - fAreaWidth * fSettings.scroll_rightrange
< event->xPosition && !fMovementStarted
&& fSettings.scroll_rightrange > 0.000001)
|| fSettings.scroll_rightrange > 0.999999) {
isSideScrollingV = true;
}
if ((fSpecs.areaStartY + fAreaHeight * fSettings.scroll_bottomrange
> event->yPosition && !fMovementStarted
&& fSettings.scroll_bottomrange > 0.000001)
|| fSettings.scroll_bottomrange > 0.999999) {
isSideScrollingH = true;
}
bool isTwoFingerScrolling = two_fingers(event)
&& fSettings.scroll_twofinger;
if (isTwoFingerScrolling) {
isSideScrollingV = true;
isSideScrollingH = fSettings.scroll_twofinger_horizontal;
}
if (!isSideScrollingV && !isSideScrollingH) {
fScrollingStarted = false;
return false;
}
TRACE("TouchpadMovement: scroll event\n");
fTapStarted = false;
fTapClicks = 0;
fTapdragStarted = false;
fValidEdgeMotion = false;
if (!fScrollingStarted) {
fScrollingStarted = true;
StartNewMovment();
}
GetScrolling(event->xPosition, event->yPosition, isTwoFingerScrolling
? fSettings.scroll_twofinger_natural_scrolling
: fSettings.scroll_reverse);
movement->wheel_ydelta = make_small(yDelta);
movement->wheel_xdelta = make_small(xDelta);
if (isSideScrollingV && !isSideScrollingH)
movement->wheel_xdelta = 0;
else if (isSideScrollingH && !isSideScrollingV)
movement->wheel_ydelta = 0;
fButtonsState = movement->buttons;
return true;
}