Skip to content

Instantly share code, notes, and snippets.

@jft7
Last active February 23, 2021 23:49
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jft7/7b3bcf54e29341475cebc920ed44b080 to your computer and use it in GitHub Desktop.
Save jft7/7b3bcf54e29341475cebc920ed44b080 to your computer and use it in GitHub Desktop.
Secondary Button Mouse stimulator for PI touch screen : long press to emulate right button, very long press for middle button
/*
MIT License
Copyright (c) 2020 jft (Cybele Services)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/*
Secondary Button Mouse stimulator for touch screen : long press to emulate right button, very long press for middle button
Any move while pressing cancels the button action (move detected when x/y value change is > mouse_sensitivity parameter)
Tested with :
-------------
- Raspberry FT5406 7inch display
- WaveShare WS170120 7inch USB display
- wch.cn USB2IIC_CTP_CONTROL
- ILITEK ILITEK-TP (Beetronics 10inch)
(now autodetect supported devices)
Install development packages:
-----------------------------
sudo apt-get install build-essential libx11-dev libxtst-dev libxi-dev x11proto-randr-dev libxrandr-dev
Compile with:
-------------
g++ -Wall -Ofast ts-mouse.cpp -o ts-mouse -lX11 -lXtst -lXext
Usage:
------
ts-mouse [DeviceName]
ts-mouse [DeviceName] [Time to click]
ts-mouse [DeviceName] [Time to click] [Time to hold]
ts-mouse [DeviceName] [Time to click] [Time to hold] [Mouse sensitivity]
To run at startup add a line in XDG autostart file "/etc/xdg/lxsession/LXDE-pi/autostart":
@/home/pi/mouse/ts-mouse
(ts-mouse file is supposed to be in /home/pi/mouse ...)
Default values:
---------------
DeviceName = "ILITEK ILITEK-TP" (Beetronics 10inch)
Time to click = .5 (500mS)
Time to hold = 4 (4 S)
Mouse sensitivity = 100 (any movement > 100 pixels cancels current click emulation)
*/
#include <fcntl.h>
#include <iostream>
#include <linux/input.h>
#include <string>
#include <unistd.h>
#include <stdlib.h>
#include <X11/extensions/XTest.h>
#include <string>
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <array>
#include <fstream>
#include <iostream>
const char *supportedDevices[] = {
"",
"Raspberry FT5406",
"WaveShare WS170120",
"ILITEK ILITEK-TP",
"wch.cn USB2IIC_CTP_CONTROL",
NULL } ;
int main(int argc, char** argv)
{
const char* eventFile;
Display* dpy = NULL;
XEvent event;
dpy = XOpenDisplay(NULL);
int i, fd;
struct input_event ie;
Window root, child;
int rootX, rootY, winX, winY;
unsigned int mask;
XQueryPointer(dpy, DefaultRootWindow(dpy), &root, &child, &rootX, &rootY, &winX, &winY, &mask);
std::array<char, 128> buffer;
std::string result;
char* Handler;
const char* deviceName = "ILITEK ILITEK-TP"; // Default to Beetronics 10inch
float time_to_click = .5; // How long to hold the button down?
float time_to_hold = 4; // Time pressed for holding click
int move_sensitivity = 100 ; // nb of pixels to detect move (to cancel click emulation)
if (argc >= 2)
supportedDevices[0] = argv[1];
if (argc >= 3)
time_to_click = atof(argv[2]);
if( argc >= 4)
time_to_hold = atof(argv[3]);
if( argc >= 5)
move_sensitivity = atoi(argv[4]);
for( i = 0 ; deviceName = supportedDevices[i], deviceName != NULL ; i++ )
{
if ( asprintf( &Handler, "cat /proc/bus/input/devices | awk -v x='%s' '$0~x,EOF' | awk '/Handler/ {print $3; exit}'", deviceName ) <= 0 )
{
std::cerr << "Couldn't start command." << std::endl;
return 1;
}
// std::cout << "Device: " << deviceName << " TimeRightClick: " << time_to_click << " TimeMiddleClick: " << time_to_hold << std::endl;
FILE* pipe = popen(Handler, "r");
if (!pipe)
{
std::cerr << "Couldn't start command." << std::endl;
return 1;
}
result = "";
while (fgets(buffer.data(), 128, pipe) != NULL)
result += buffer.data();
pclose(pipe);
if( result.length() > 2 )
{
result = result.substr(0, result.length() - 1) ;
break;
}
}
if( deviceName == NULL )
{
std::cerr << "Couldn't find supported device." << std::endl;
return 1;
}
result = "/dev/input/" + result;
// std::cout << "Using file:" << result << " for device: " << deviceName << std::endl;
eventFile = result.c_str();
if ((fd = open(eventFile, O_RDONLY)) == -1) {
perror("opening device");
return 1;
}
auto time_start_sec = ie.time.tv_sec;
auto time_stop_sec = ie.time.tv_sec;
auto time_start_usec = ie.time.tv_usec;
auto time_stop_usec = ie.time.tv_usec;
auto time_stop_usec_adj = ie.time.tv_usec;
auto time_real = ie.time.tv_usec;
float time_elapsed_adj;
int pressed = 0;
int cursor_x, cursor_y ;
// std::cout << "Ready for mouse events..." << std::endl;
while (true) {
// Grab mouse events:
read(fd, &ie, sizeof(struct input_event));
// std::cout << "Type: " << ie.type << " Code: " << ie.code << " Value: " << ie.value << " Time: " << ie.time.tv_sec << "." << ie.time.tv_usec << std::endl;
if (pressed && ie.type == EV_KEY && ie.code == BTN_TOUCH && ie.value == 0) {
pressed = 0;
// Timer from mouse events is used
time_real = ie.time.tv_sec;
time_stop_sec = time_real - time_start_sec;
time_stop_usec = ie.time.tv_usec;
if (time_stop_usec < time_start_usec) {
time_stop_usec_adj = time_stop_usec + (1000000 - time_start_usec);
if (time_real - time_start_sec > 0)
time_stop_sec = time_stop_sec - 1;
}
else
time_stop_usec_adj = time_stop_usec - time_start_usec;
time_elapsed_adj = float(time_stop_sec) + (float(time_stop_usec_adj) / 1000000);
// std::cout << "Released! ... Time held: " << time_elapsed_adj << " seconds." << " Time: " << time_real << "." << time_stop_usec << std::endl;
if (time_elapsed_adj >= time_to_click) {
if (time_elapsed_adj >= time_to_hold)
{
// std::cout << "Middle button clicked!" << std::endl;
XQueryPointer(dpy, RootWindow(dpy, 0), &event.xbutton.root,
&event.xbutton.window, &event.xbutton.x_root,
&event.xbutton.y_root, &event.xbutton.x, &event.xbutton.y,
&event.xbutton.state);
usleep(1000);
XTestFakeButtonEvent(dpy, 2, True, CurrentTime);
usleep(1000);
XQueryPointer(dpy, RootWindow(dpy, 0), &event.xbutton.root,
&event.xbutton.window, &event.xbutton.x_root,
&event.xbutton.y_root, &event.xbutton.x, &event.xbutton.y,
&event.xbutton.state);
usleep(1000);
XTestFakeButtonEvent(dpy, 2, False, CurrentTime);
usleep(1000);
XQueryPointer(dpy, RootWindow(dpy, 0), &event.xbutton.root,
&event.xbutton.window, &event.xbutton.x_root,
&event.xbutton.y_root, &event.xbutton.x, &event.xbutton.y,
&event.xbutton.state);
}
else {
// std::cout << "Right button clicked!" << std::endl;
XQueryPointer(dpy, RootWindow(dpy, 0), &event.xbutton.root,
&event.xbutton.window, &event.xbutton.x_root,
&event.xbutton.y_root, &event.xbutton.x, &event.xbutton.y,
&event.xbutton.state);
usleep(1000);
XTestFakeButtonEvent(dpy, 3, True, CurrentTime);
usleep(1000);
XQueryPointer(dpy, RootWindow(dpy, 0), &event.xbutton.root,
&event.xbutton.window, &event.xbutton.x_root,
&event.xbutton.y_root, &event.xbutton.x, &event.xbutton.y,
&event.xbutton.state);
usleep(1000);
XTestFakeButtonEvent(dpy, 3, False, CurrentTime);
usleep(1000);
XQueryPointer(dpy, RootWindow(dpy, 0), &event.xbutton.root,
&event.xbutton.window, &event.xbutton.x_root,
&event.xbutton.y_root, &event.xbutton.x, &event.xbutton.y,
&event.xbutton.state);
}
}
}
// Mouse button is 'clicked' (initial press):
if (ie.type == EV_KEY && ie.code == BTN_TOUCH && ie.value == 1) {
pressed = 1;
cursor_x = 0;
cursor_y = 0;
time_start_sec = ie.time.tv_sec;
time_start_usec = ie.time.tv_usec;
// std::cout << "\nPressed! ... Time: " << time_start_sec << "." << time_start_usec << std::endl;
}
// Mouse has moved ... (cancel)
if (pressed && ie.type == EV_ABS ){
if(ie.code == ABS_MT_POSITION_X) {
if( cursor_x == 0 )
cursor_x = ie.value ;
else if( abs( ie.value - cursor_x ) > move_sensitivity )
pressed = 0;
}
else if(ie.code == ABS_MT_POSITION_Y) {
if( cursor_y == 0 )
cursor_y = ie.value ;
else if( abs( ie.value - cursor_y ) > move_sensitivity )
pressed = 0;
}
if( !pressed ) {
time_start_sec = ie.time.tv_sec;
time_start_usec = ie.time.tv_usec;
// std::cout << "\nMove! ... Time: " << time_start_sec << "." << time_start_usec << std::endl;
}
}
}
XCloseDisplay(dpy);
return 0;
}
@kleag
Copy link

kleag commented Dec 2, 2020

Hi, could you please include a license indication in the header comment such that your code can be used, modified and redistributed? If you wish the highest freedom as possible, I would suggest MIT or BSD. I you want to force others to keep your code free, use the GPL or LGPL.

If you wish, you could also add that it has also been tested with "wch.cn USB2IIC_CTP_CONTROL".

@jft7
Copy link
Author

jft7 commented Dec 2, 2020

MIT license added (as suggested)
Add "ILITEK ILITEK TP" and "wch.cn USB2IIC_CTP_CONTROL" as tested touchscreens
Change default display to (ILITEK ILITEK-TP Beetronics 10inch) and decrease sensitivity (else small move on high def display cancels mouse click ...)
For information:
Tested on raspios-buster
Measured cpu with raspberry < 1.3% while moving window on 3b+, 0% idle

@kleag
Copy link

kleag commented Dec 2, 2020

Great! Quick reaction. Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment