I am trying to read mouse events from the /dev/input/mice file. I am able to parse the 3 byte mouse input for getting the three button states and the increments in X and Y coordinates. However, the mouse input when I scroll up is identical to that when I scroll down. How do I distinguish a scroll up event from a scroll down event? Are there any ioctls that can do any required configuration so that I get different inputs from the mouse on these two events?
The following is a simple program to see the input from a mouse when a mouse event occurs. Scroll up and scroll down events cause the same output to be printed by this program (namely, 8 0 0).
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
int main(void) {
int mouse_fd = open("/dev/input/mice", O_RDONLY | O_NONBLOCK);
signed char input[4];
ssize_t rd_cnt;
if(mouse_fd < 0)
{
perror("Could not open /dev/input/mice");
exit(EXIT_FAILURE);
}
while(true)
{
errno = 0;
rd_cnt = read(mouse_fd, input, 4);
if(rd_cnt <= 0 && errno != EAGAIN)
{
perror("Mouse read error:");
exit(EXIT_FAILURE);
}
else
{
for(int i = 0; i < rd_cnt; i++)
{
printf("%d", input[i]);
if(i == rd_cnt - 1)
{
printf("\n");
}
else
{
printf("\t");
}
}
}
}
return 0;
}
An alternative would be to use SDL2.
I've managed to mash together an example of reading mouse inputs with SDL, so take what you like from it.
#include <SDL2/SDL.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
char* itoa(int i, char b[]){
char const digit[] = "0123456789";
char* p = b;
if(i<0){
*p++ = '-';
i *= -1;
}
int shifter = i;
do{ //Move to where representation ends
++p;
shifter = shifter/10;
}while(shifter);
*p = '\0';
do{ //Move back, inserting digits as u go
*--p = digit[i%10];
i = i/10;
}while(i);
return b;
}
int main()
{
//initialize the window
bool quit = false;
SDL_Init(SDL_INIT_VIDEO);
SDL_Window* window = SDL_CreateWindow("Mouse Events", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 480, 0);
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0);
// the event that will occur when a mouse event happens
SDL_Event event;
while(!quit)
{
while (SDL_PollEvent(&event)) //returns one is there is a pending event (if an event happens)
{
switch(event.type)
{
case SDL_QUIT: //if the window is exited
quit = true;
break;
case SDL_MOUSEBUTTONDOWN:
switch (event.button.button)
{
case SDL_BUTTON_LEFT:
SDL_ShowSimpleMessageBox(0, "Click", "Left button was pressed!", window);
break;
case SDL_BUTTON_RIGHT:
SDL_ShowSimpleMessageBox(0, "Click", "Right button was pressed!", window);
break;
}
break;
case SDL_MOUSEWHEEL:
if(event.wheel.y == -1) //negative means the scroll wheel has gone away from the user
{
SDL_ShowSimpleMessageBox(0, "Wheel Event", "You rolled away from yourself!", window);
} else if (event.wheel.y == 1) //vice-versa
{
SDL_ShowSimpleMessageBox(0, "Wheel Event", "You rolled towards yourself!", window);
}
}
}
//do some SDL cleanup
SDL_Rect dstrect = { 288, 208, 64, 64 };
SDL_RenderClear(renderer);
SDL_RenderPresent(renderer);
}
}
The event.wheel type can be found here: https://wiki.libsdl.org/SDL_MouseWheelEvent
Hope this is of some use to you!
If you don't want to use SDL2, it may be worth have a look in the source of the library to see what it's doing.
Related
I'm trying to get the terminal window size, even when I resize the window, I'm using termcaps for this, the problem is when I resize the window, the values of lines and columns stays the same instead of updating, I also tried using ncurses's LINES and COLS globals, but the same thing happens.
Here is a minimal reproductible example:
#include <ncurses.h>
#include <term.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
char * term_type = getenv("TERM");
int ret;
int li_cap;
int co_cap;
if (!term_type)
{
write(2, "TERM env must be set\n", 21);
return (-1);
}
if ((ret = tgetent(NULL, term_type)) == -1)
{
write(2, "Could not access to the termcap database\n", 41);
return (-1);
}
if (!ret)
{
write(2, "This terminal is not supported by termcaps\n", 43);
return (-1);
}
while (1)
{
sleep(1);
li_cap = tgetnum("li");
co_cap = tgetnum("co");
printf("%d %d\n", li_cap, co_cap);
}
return (0);
}
So when I resize the window inside the loop, the values stay the same, I want to get the lines and colums in real time, how could I do this with termcaps?
The termcap data and the environment variables COLUMNS and LINES are unreliable and aren't updated upon terminal resizing, especially during program execution. There is another solution for POSIX systems where you can:
retrieve the size of the terminal window with ioctl(0, TIOCGWINSZ, &ws)
register a signal handler to get notified of terminal size changes.
Here is a demonstration program:
#include <stdio.h>
#include <signal.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <unistd.h>
static volatile unsigned char term_size_updated;
static void term_resize() {
term_size_updated = 1;
}
static void term_get_size(int *cols, int *rows) {
struct winsize ws;
/* get screen dimensions from (pseudo) tty ioctl */
if (ioctl(0, TIOCGWINSZ, &ws) == 0) {
*cols = ws.ws_col;
*rows = ws.ws_row;
} else {
*cols = *rows = -1;
}
}
int main() {
struct sigaction sig;
int cols, rows;
/* set up terminal resize callback */
sig.sa_handler = term_resize;
sigemptyset(&sig.sa_mask);
sig.sa_flags = 0;
sigaction(SIGWINCH, &sig, NULL);
term_size_updated = 1;
for (;;) {
if (term_size_updated) {
term_size_updated = 0;
term_get_size(&cols, &rows);
fprintf(stderr, "term_resize: cols=%d, rows=%d\n", cols, rows);
}
sleep(1);
}
return 0;
}
First of all, I don't know if I can explain well my problem or you can get it in the appropriate way. But I will try to make it clear for you.
In fact, I have two different C programs.
The first one is a simple loop print of a message on the console :
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main ()
{
while(1)
{
printf("WAITING\n");
sleep(1);
}
}
The second one is a blocking program that waits for an event ( press button ) to turn on led in my embedded board.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/select.h>
#include <sys/time.h>
#include <errno.h>
#include <linux/input.h>
#define BTN_FILE_PATH "/dev/input/event0"
#define LED_PATH "/sys/class/leds"
#define green "green"
void change_led_state(char *led_path, int led_value)
{
char lpath[64];
FILE *led_fd;
strncpy(lpath, led_path, sizeof(lpath) - 1);
lpath[sizeof(lpath) - 1] = '\0';
led_fd = fopen(lpath, "w");
if (led_fd == NULL) {
fprintf(stderr, "simplekey: unable to access led\n");
return;
}
fprintf(led_fd, "%d\n", led_value);
fclose(led_fd);
}
void reset_leds(void)
{
change_led_state(LED_PATH "/" green "/brightness", 0);
}
int configure_leds(void)
{
FILE *l_fd;
FILE *r_fd;
char *none_str = "none";
/* Configure leds for hand control */
r_fd = fopen(LED_PATH "/" green "/trigger", "w");
fprintf(r_fd, "%s\n", none_str);
fclose(r_fd);
/* Switch off leds */
reset_leds();
return 0;
}
void eval_keycode(int code)
{
static int green_state = 0;
switch (code) {
case 260:
printf("BTN left pressed\n");
/* figure out red state */
green_state = green_state ? 0 : 1;
change_led_state(LED_PATH "/" green "/brightness", green_state);
break;
}
}
int main(void)
{
int file;
/* how many bytes were read */
size_t rb;
int ret;
int yalv;
/* the events (up to 64 at once) */
struct input_event ev[64];
char *str = BTN_FILE_PATH;
printf("Starting simplekey app\n");
ret = configure_leds();
if (ret < 0)
exit(1);
printf("File Path: %s\n", str);
if((file = open(str, O_RDONLY)) < 0) {
perror("simplekey: File can not open");
exit(1);
}
for (;;) {
/* Blocking read */
rb= read(file, &ev, sizeof(ev));
if (rb < (int) sizeof(struct input_event)) {
perror("simplekey: short read");
exit(1);
}
for (yalv = 0;
yalv < (int) (rb / sizeof(struct input_event));
yalv++) {
if (ev[yalv].type == EV_KEY) {
printf("%ld.%06ld ",
ev[yalv].time.tv_sec,
ev[yalv].time.tv_usec);
printf("type %d code %d value %d\n",
ev[yalv].type,
ev[yalv].code, ev[yalv].value);
/* Change state on button pressed */
if (ev[yalv].value == 0)
eval_keycode(ev[yalv].code);
}
}
}
close(file);
reset_leds();
exit(0);
}
When I execute the second code, the program starts waiting for the event to switch on/off the led.
My question is :
How can I make interaction between the two programs ? I want to execute the firs one --> It starts printing for me " WAITING " until I press the BUTTON --> the LED turn on --> and then it goes back to the first program and re-start printing " WAITING " on the console.
I don't know if I explained well the issue but I hope that you can help me! Thank you.
You need a communication mechanism between your two programs. This is also known als inter-process communication.
Generally, you have several options to achieve this (depending on the operating system you are using, not all of them may be available):
Shared memory / shared files
Message passing (e.g. via sockets)
Pipes
Signals
A helpful introduction can be found here.
I'm trying to write a program using ncurses that polls various file descriptors in the main loop. I managed to trim it down to an example with a single file descriptor, and this expected behavior:
There are two windows, managed by the panel library.
Pressing space moves the cursor right, wrapping around when it reaches the end of the first window.
Pressing 't' changes the text in the top left corner.
Pressing 'q' quits the program.
Code:
#include <stdio.h>
#include <stdlib.h>
#include <panel.h>
#include <unistd.h>
#include <poll.h>
#include <sys/eventfd.h>
#define POLL_STDIN
int main() {
int event_fd, cursor_x, cursor_y, ch, n_fds;
bool trigd;
uint64_t event;
WINDOW *win_a, *win_b;
PANEL *panel_a, *panel_b;
event_fd = eventfd(0, 0);
if (event_fd < 0) {
perror("eventfd");
exit(1);
}
struct pollfd poll_fds[2];
poll_fds[0].fd = STDIN_FILENO;
#ifdef POLL_STDIN
poll_fds[0].events = POLLIN;
#else
poll_fds[0].events = 0;
#endif
poll_fds[1].fd = event_fd;
poll_fds[1].events = POLLIN;
initscr();
halfdelay(1);
noecho();
win_a = newwin(10, 10, 0, 0);
win_b = newwin(10, 10, 0, 10);
panel_a = new_panel(win_a);
panel_b = new_panel(win_b);
cursor_x = 0;
cursor_y = 0;
wmove(win_a, cursor_y, cursor_x);
do {
for (int i=0; i<10; ++i) {
for (int j=0; j<10; ++j) {
mvwaddch(win_a, i, j, '.');
mvwaddch(win_b, i, j, '_');
}
}
mvwprintw(win_a, 0, 0, trigd ? "foo" : "bar");
update_panels();
doupdate();
wmove(win_a, cursor_y, cursor_x);
#ifdef POLL_STDIN
n_fds = poll(poll_fds, 2, -1);
#else
n_fds = poll(poll_fds, 2, 0);
#endif
if (n_fds < 0) {
perror("poll");
break;
}
ch = wgetch(win_a);
if (poll_fds[1].revents & POLLIN) {
if (read(event_fd, &event, 8) != 8) {
perror("read");
break;
}
trigd = !trigd;
}
if (' ' == ch) {
cursor_x = (cursor_x + 1) % 10;
} else if ('t' == ch) {
event = 1;
if (write(event_fd, &event, 8) != 8) {
perror("write");
break;
}
}
} while ('q' != ch);
endwin();
return 0;
}
If POLL_STDIN is not defined, the program works as expected. If it is defined, the program works almost the same, but the cursor is displayed in the lower right corner in window B, and doesn't move. After pressing t the cursor temporarily moves to the expected position.
I have looked at the program after running the preprocessor and found nothing unexpected, only mvaddch gets expanded.
I just think that the busy-waiting version is kind of inelegant, and also would like to know why the cursor is displayed in the wrong place after a seemingly unrelated change.
EDIT:
I've figured out that calling getch is what makes the cursor to show. After using nodelay on both windows, and calling getch twice, the program now works in both versions, and the second getch can be made conditional. Here's the updated code:
#include <stdio.h>
#include <stdlib.h>
#include <panel.h>
#include <unistd.h>
#include <poll.h>
#include <sys/eventfd.h>
int main() {
int event_fd, cursor_x, cursor_y, ch, n_fds;
bool trigd;
uint64_t event;
WINDOW *win_a, *win_b;
PANEL *panel_a, *panel_b;
event_fd = eventfd(0, 0);
if (event_fd < 0) {
perror("eventfd");
exit(1);
}
struct pollfd poll_fds[2];
poll_fds[0].fd = STDIN_FILENO;
poll_fds[0].events = POLLIN;
poll_fds[1].fd = event_fd;
poll_fds[1].events = POLLIN;
initscr();
cbreak();
noecho();
win_a = newwin(10, 10, 0, 0);
win_b = newwin(10, 10, 0, 10);
panel_a = new_panel(win_a);
panel_b = new_panel(win_b);
cursor_x = 0;
cursor_y = 0;
wmove(win_a, cursor_y, cursor_x);
do {
for (int i=0; i<10; ++i) {
for (int j=0; j<10; ++j) {
mvwaddch(win_a, i, j, '.');
mvwaddch(win_b, i, j, '_');
}
}
mvwprintw(win_a, 0, 0, trigd ? "foo" : "bar");
update_panels();
doupdate();
wmove(win_a, cursor_y, cursor_x);
wrefresh(win_a);
n_fds = poll(poll_fds, 2, -1);
if (n_fds < 0) {
perror("poll");
break;
}
if (poll_fds[0].revents & POLLIN) {
ch = wgetch(win_a);
}
if (poll_fds[1].revents & POLLIN) {
if (read(event_fd, &event, 8) != 8) {
perror("read");
break;
}
trigd = !trigd;
}
if (' ' == ch) {
cursor_x = (cursor_x + 1) % 10;
} else if ('t' == ch) {
event = 1;
if (write(event_fd, &event, 8) != 8) {
perror("write");
break;
}
}
} while ('q' != ch);
endwin();
return 0;
}
I'm not sure if this is the best way to show the cursor, but I'll post it as the answer if no one else posts one.
EDIT2:
Edited code for posterity after #Thomas Dickey's comment.
I think adding a wrefresh is the right way to show the cursor, judging by this answer (EDIT updated after #Thomas Dickey's comment: wgetch is not necessary). So it boils down to:
nodelay(win_a); // to make wgetch non-blocking
/* ... */
wrefresh(win_a); // to show the cursor
poll(poll_fds, 2, -1);
if (poll_fds[0].revents & POLLIN) {
ch = wgetch(win_a); // to get the actual character
}
I have ncurses program where I need instant response to user input and term resize and 1 sec delay between redraws.
By using sleep(1) I got instant redraw on startup and term resize but 1 sec delay on user input.
By using timeout(1 * 1000) and getch() I get instant response for input but 1 sec redraw delay on startup and resize.
Here is example program to demonstrate the problem:
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <ncurses.h>
static sig_atomic_t resize;
void sighandler(int sig) {
if (sig == SIGWINCH)
resize = 1;
}
int main(int argc, char *argv[]) {
double delay = 1.0;
char key = ERR;
WINDOW *testwin;
if (argc > 1)
delay = strtod(argv[1], NULL);
signal(SIGWINCH, sighandler);
initscr();
timeout(delay * 1000);
testwin = newwin(LINES, COLS, 0, 0);
while (key != 'q') {
if (key != ERR)
resize = 1;
if (resize) {
endwin();
refresh();
clear();
werase(testwin);
wresize(testwin, LINES, COLS);
resize = 0;
}
box(testwin, 0, 0);
wnoutrefresh(testwin);
doupdate();
key = getch();
}
delwin(testwin);
endwin();
return EXIT_SUCCESS;
}
I'm thinking about select and/or threads: one thread could monitor the resize while the other one will wait for user input.
You can synchronize threads with a select, which can wait on multiple file descriptors: for example you can write on a pipe when you detect an event to wake-up the main process. Cool thing is you can also set a timeout to be woken-up even if no event happened (then you can put a timeout of one second to trigger the redraw).
I managed to solve the resize delay by removing clear();
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <ncurses.h>
static sig_atomic_t resize;
void sighandler(int sig) {
if (sig == SIGWINCH)
resize = 1;
}
int main(int argc, char *argv[]) {
double delay = 1.0;
char key = ERR;
WINDOW *testwin;
if (argc > 1)
delay = strtod(argv[1], NULL);
signal(SIGWINCH, sighandler);
initscr();
timeout(delay * 1000);
testwin = newwin(LINES, COLS, 0, 0);
while (key != 'q') {
key = getch();
if (key != ERR)
resize = 1;
if (resize) {
endwin();
refresh();
werase(testwin);
wresize(testwin, LINES, COLS);
resize = 0;
}
box(testwin, 0, 0);
wnoutrefresh(testwin);
doupdate();
}
delwin(testwin);
endwin();
return EXIT_SUCCESS;
}
I want to log my mouse click positions. I have tried this;
#include <stdio.h>
#include <stddef.h>
#include <X11/Xlib.h>
#include <assert.h>
#include <unistd.h>
#include <signal.h>
int working = 1;
void signal_callback_handler(int signum) {
working = 0;
}
int main () {
signal(SIGINT, signal_callback_handler);
signal(SIGTSTP, signal_callback_handler);
signal(SIGTERM, signal_callback_handler);
Display *d = XOpenDisplay(NULL);
assert(d);
XSelectInput(d, DefaultRootWindow(d), ButtonPressMask);
while(working) {
XEvent e;
XNextEvent(d,&e);
if (e.type == ButtonPress) {
printf("%dx%d",e.xbutton.x,e.xbutton.y);
}
}
return 0;
}
But I am seeing this error:
X Error of failed request: BadAccess (attempt to access private resource denied)
Major opcode of failed request: 2 (X_ChangeWindowAttributes)
Serial number of failed request: 7
Current serial number in output stream: 7
What is wrong with my code, and how can I fix it?
Update
I have researched this a little bit more, and got some help from the folks in #xorg-dev. It seems like it is impossible to do with regular Xlib, because only one client can register for button press on a window. In this case, my WM already registered, therefore I get bad access. It seems like this can be done using X input extensions and by listening XI_RawButtonPress Event, which I am still trying to figure out how to do. Here is what I have so far;
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/extensions/XInput2.h>
#include <signal.h>
#include <assert.h>
int working = 1;
void signal_callback_handler(int signum) {
working = 0;
}
int main() {
signal(SIGINT, signal_callback_handler);
signal(SIGTSTP, signal_callback_handler);
signal(SIGTERM, signal_callback_handler);
/* Connect to the X server */
Display *dpy = XOpenDisplay(NULL);
assert(dpy);
/* XInput Extension available? */
int opcode, event, error;
if (!XQueryExtension(dpy, "XInputExtension", &opcode, &event, &error)) {
printf("X Input extension not available.\n");
return -1;
}
/* Which version of XI2? We support 2.0 */
int major = 2, minor = 0;
if (XIQueryVersion(dpy, &major, &minor) == BadRequest) {
printf("XI2 not available. Server supports %d.%d\n", major, minor);
return -1;
}
XIEventMask eventmask;
unsigned char mask[1] = { 0 }; /* the actual mask */
eventmask.deviceid = 2;
eventmask.mask_len = sizeof(mask); /* always in bytes */
eventmask.mask = mask;
/* now set the mask */
XISetMask(mask, XI_RawButtonPress);
/* select on the window */
XISelectEvents(dpy, DefaultRootWindow(dpy), &eventmask, 1);
while(working) {
XEvent ev;
XNextEvent(dpy, &ev);
if (ev.xcookie.type == GenericEvent &&
ev.xcookie.extension == opcode &&
XGetEventData(dpy, &ev.xcookie))
{
switch(ev.xcookie.evtype)
{
case XI_RawButtonPress:
printf("RawButtonPress");
break;
}
}
XFreeEventData(dpy, &ev.xcookie);
}
}
However, I get this error;
X Error of failed request: XI_BadDevice (invalid Device parameter)
Major opcode of failed request: 131 (XInputExtension)
Minor opcode of failed request: 46 ()
Device id in failed request: 0xad
Serial number of failed request: 15
Current serial number in output stream: 15
Update 2
I have tried to do this with ButtonRelaseEvent, but I am not getting any event. XNextEvent blocks forever, no matter where I click/relase button. Here are the codes;
#include <stdio.h>
#include <stddef.h>
#include <X11/Xlib.h>
#include <assert.h>
#include <unistd.h>
#include <signal.h>
int working = 1;
void signal_callback_handler(int signum) {
working = 0;
}
int main () {
signal(SIGINT, signal_callback_handler);
signal(SIGTSTP, signal_callback_handler);
signal(SIGTERM, signal_callback_handler);
Display *d = XOpenDisplay(NULL);
assert(d);
XSelectInput(d,DefaultRootWindow(d), ButtonReleaseMask);
while(working) {
XEvent e;
XNextEvent(d, &e);
printf("Something Occured");
if (e.type == ButtonRelease) {
printf("%dx%d",e.xbutton.x,e.xbutton.y);
}
}
return 0;
}
Try XWindowEvent instead of XNextEvent.
For example to grab mouse you can do this:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/cursorfont.h>
int main(){
Display* display;
int screen_num;
Screen *screen;
Window root_win;
XEvent report;
XButtonEvent *xb = (XButtonEvent *)&report;
int i;
Cursor cursor;
display = XOpenDisplay(0);
if (display == NULL){
perror("Cannot connect to X server");
exit (-1);
}
screen_num = DefaultScreen(display);
screen = XScreenOfDisplay(display, screen_num);
root_win = RootWindow(display, XScreenNumberOfScreen(screen));
cursor = XCreateFontCursor(display, XC_crosshair);
i = XGrabPointer(display, root_win, False,
ButtonReleaseMask | ButtonPressMask|Button1MotionMask, GrabModeSync,
GrabModeAsync, root_win, cursor, CurrentTime);
if(i != GrabSuccess){
perror("Can't grab the mouse");
exit(-1);
}
for(i = 0; i < 10; i++){
XAllowEvents(display, SyncPointer, CurrentTime);
XWindowEvent(display, root_win, ButtonPressMask | ButtonReleaseMask, &report);
switch(report.type){
case ButtonPress:
printf("Press # (%d, %d)\n", xb->x_root, xb->y_root);
break;
case ButtonRelease:
printf("Release # (%d, %d)\n", xb->x_root, xb->y_root);
break;
}
}
XFlush(display);
XUngrabServer(display);
XCloseDisplay( display );
return 0;
}
Yes, from x11 protocol spec:
Multiple clients can select input on the same window; their
event-masks are disjoint. When an event is generated, it will be
reported to all interested clients. However, only one client at a time
can select for SubstructureRedirect, only one client at a time can
select for ResizeRedirect, and only one client at a time can select
for ButtonPress. An attempt to violate these restrictions results in
an Access error.
However, it is allowed for multiple clients to select ButtonRelease event - I just checked with two clients and both receive events.