Browse Source

Fix position for click and drag with EmulateMB

If you have the setting "Emulate middle mouse button" turned on, a click
and drag can fail if it is done very quickly. The position of the
initial click will be incorrect in such a case because the timeout will
delay events.
tags/v1.10.90
Alex Tanskanen 4 years ago
parent
commit
9197db186b
3 changed files with 123 additions and 15 deletions
  1. 60
    2
      tests/unit/emulatemb.cxx
  2. 60
    12
      vncviewer/EmulateMB.cxx
  3. 3
    1
      vncviewer/EmulateMB.h

+ 60
- 2
tests/unit/emulatemb.cxx View File

printf("OK\n"); printf("OK\n");
} }


void testTimeoutDuringDrag()
void testTimeoutAndDrag()
{ {
TestClass test; TestClass test;


printf("OK\n"); printf("OK\n");
} }


void testDragAndTimeout()
{
TestClass test;

printf("%s: ", __func__);

emulateMiddleButton.setParam(true);
test.filterPointerEvent(rfb::Point(10, 10), left);
test.filterPointerEvent(rfb::Point(30, 30), left);
usleep(100000); //0.1s
rfb::Timer::checkTimeouts();

ASSERT_EQ(test.results.size(), 3);

ASSERT_EQ(test.results[0].pos.x, 10);
ASSERT_EQ(test.results[0].pos.y, 10);
ASSERT_EQ(test.results[0].mask, empty);

ASSERT_EQ(test.results[1].pos.x, 10);
ASSERT_EQ(test.results[1].pos.y, 10);
ASSERT_EQ(test.results[1].mask, left);

ASSERT_EQ(test.results[2].pos.x, 30);
ASSERT_EQ(test.results[2].pos.y, 30);
ASSERT_EQ(test.results[2].mask, left);

printf("OK\n");
}

void testDragAndRelease()
{
TestClass test;

printf("%s: ", __func__);

emulateMiddleButton.setParam(true);
test.filterPointerEvent(rfb::Point(10, 10), left);
test.filterPointerEvent(rfb::Point(20, 20), empty);

ASSERT_EQ(test.results.size(), 3);

ASSERT_EQ(test.results[0].pos.x, 10);
ASSERT_EQ(test.results[0].pos.y, 10);
ASSERT_EQ(test.results[0].mask, empty);

ASSERT_EQ(test.results[1].pos.x, 10);
ASSERT_EQ(test.results[1].pos.y, 10);
ASSERT_EQ(test.results[1].mask, left);

ASSERT_EQ(test.results[2].pos.x, 20);
ASSERT_EQ(test.results[2].pos.y, 20);
ASSERT_EQ(test.results[2].mask, empty);

printf("OK\n");
}

int main(int argc, char** argv) int main(int argc, char** argv)
{ {
testDisabledOption(); testDisabledOption();
testBothPressAfterLeftTimeout(); testBothPressAfterLeftTimeout();
testBothPressAfterRightTimeout(); testBothPressAfterRightTimeout();


testTimeoutDuringDrag();
testTimeoutAndDrag();


testDragAndTimeout();
testDragAndRelease();


return 0; return 0;
} }

+ 60
- 12
vncviewer/EmulateMB.cxx View File

int action1, action2; int action1, action2;
int lastState; int lastState;


// Just pass through events if the emulate setting is disabled
if (!emulateMiddleButton) { if (!emulateMiddleButton) {
sendPointerEvent(pos, buttonMask); sendPointerEvent(pos, buttonMask);
return; return;
throw rfb::Exception(_("Invalid state for 3 button emulation")); throw rfb::Exception(_("Invalid state for 3 button emulation"));


action1 = stateTab[state][btstate][0]; action1 = stateTab[state][btstate][0];
if (action1 != 0)
sendAction(pos, buttonMask, action1);

if (action1 != 0) {
// Some presses are delayed, that means we have to check if that's
// the case and send the position corresponding to where the event
// first was initiated
if ((stateTab[state][4][2] >= 0) && action1 > 0)
// We have a timeout state and a button press (a delayed press),
// always use the original position when leaving a timeout state,
// whether the timeout was triggered or not
sendAction(origPos, buttonMask, action1);
else
// Normal non-delayed event
sendAction(pos, buttonMask, action1);
}


action2 = stateTab[state][btstate][1]; action2 = stateTab[state][btstate][1];
if (action2 != 0)
sendAction(pos, buttonMask, action2);


if ((action1 == 0) && (action2 == 0)) {
buttonMask &= ~0x5;
buttonMask |= emulatedButtonMask;
// In our case with the state machine, action2 always occurs during a button
// release but if this change we need handle action2 accordingly
if (action2 != 0) {
if ((stateTab[state][4][2] >= 0) && action2 > 0)
sendAction(origPos, buttonMask, action2);
else
// Normal non-delayed event
sendAction(pos, buttonMask, action2);
}

// Still send a pointer move event even if there are no actions.
// However if the timer is running then we are supressing _all_
// events, even movement. The pointer's actual position will be
// sent once the timer fires or is abandoned.
if ((action1 == 0) && (action2 == 0) && !timer.isStarted()) {
buttonMask = createButtonMask(buttonMask);
sendPointerEvent(pos, buttonMask); sendPointerEvent(pos, buttonMask);
} }


if (lastState != state) { if (lastState != state) {
timer.stop(); timer.stop();


if (stateTab[state][4][2] >= 0)
if (stateTab[state][4][2] >= 0) {
// We need to save the original position so that
// drags start from the correct position
origPos = pos;
timer.start(50); timer.start(50);
}
} }
} }


bool EmulateMB::handleTimeout(rfb::Timer *t) bool EmulateMB::handleTimeout(rfb::Timer *t)
{ {
int action1, action2; int action1, action2;
int buttonMask;


if (&timer != t) if (&timer != t)
return false; return false;
if ((state > 10) || (state < 0)) if ((state > 10) || (state < 0))
throw rfb::Exception(_("Invalid state for 3 button emulation")); throw rfb::Exception(_("Invalid state for 3 button emulation"));


// Timeout shouldn't trigger when there's no timeout action
assert(stateTab[state][4][2] >= 0); assert(stateTab[state][4][2] >= 0);


action1 = stateTab[state][4][0]; action1 = stateTab[state][4][0];
if (action1 != 0) if (action1 != 0)
sendAction(lastPos, lastButtonMask, action1);
sendAction(origPos, lastButtonMask, action1);


action2 = stateTab[state][4][1]; action2 = stateTab[state][4][1];
if (action2 != 0) if (action2 != 0)
sendAction(lastPos, lastButtonMask, action2);
sendAction(origPos, lastButtonMask, action2);

buttonMask = lastButtonMask;

// Pointer move events are not sent when waiting for the timeout.
// However, we can't let the position get out of sync so when
// the pointer has moved we have to send the latest position here.
if (!origPos.equals(lastPos)) {
buttonMask = createButtonMask(buttonMask);
sendPointerEvent(lastPos, buttonMask);
}


state = stateTab[state][4][2]; state = stateTab[state][4][2];


else else
emulatedButtonMask |= (1 << (action - 1)); emulatedButtonMask |= (1 << (action - 1));


buttonMask &= ~0x5;
buttonMask |= emulatedButtonMask;
buttonMask = createButtonMask(buttonMask);
sendPointerEvent(pos, buttonMask); sendPointerEvent(pos, buttonMask);
} }

int EmulateMB::createButtonMask(int buttonMask)
{
// Unset left and right buttons in the mask
buttonMask &= ~0x5;

// Set the left and right buttons according to the action
return buttonMask |= emulatedButtonMask;
}

+ 3
- 1
vncviewer/EmulateMB.h View File

private: private:
void sendAction(const rfb::Point& pos, int buttonMask, int action); void sendAction(const rfb::Point& pos, int buttonMask, int action);


int createButtonMask(int buttonMask);

private: private:
int state; int state;
int emulatedButtonMask; int emulatedButtonMask;
int lastButtonMask; int lastButtonMask;
rfb::Point lastPos;
rfb::Point lastPos, origPos;
rfb::Timer timer; rfb::Timer timer;
}; };



Loading…
Cancel
Save