I think I may have a threading problem in c, but I'm not sure.
My goal is to execute two separate functions inside a while(1) loop as such:
One of these functions is kbget() to retrieve the key pressed in a terminal in non-canonical mode.
The second one is to constantly get the terminal window size with the ioctl(1, TIOCGWINSZ,...) function.
It normally doesn't work because the while(1) loop stops to get a keypress from the user before executing the second function to reevaluate the terminal window size. If the terminal window is resized before a key is pressed, the function to evaluate its size isn't executed unless a random key is pressed again.
In other words, resizing the terminal window doesn't update the size values in the Window struct below unless a key is pressed.
I want the program to update the y_size & x_size values 'live' as the terminal is resized.
Here's the issue in code without POSIX threads:
Executing with :
gcc -Wall scr.h main.c -o main && ./main
(scr.h below has kbget() to change terminal mode):
main.c:
#include "scr.h"
#include <sys/ioctl.h>
#define gotoyx(y, x) printf("\033[%d;%dH", (y), (x)) // equivalent to move(y, x) in ncurses
#define del_from_cursor(x) printf("\033[%dX", (x)) // delete x characters from cursor position
typedef struct {
int y_size;
int x_size;
} Window;
int main(void)
{
printf("\033[?1049h\033[2J\033[H"); // remember position & clear screen
gotoyx(1, 10);
printf("Press <ESC> to stop program.");
gotoyx(2, 10);
printf("Resizing the terminal window does not 'automatically' update the size shown on screen");
Window w;
struct winsize w_s;
while (1) {
// evaluate terminal size
if (!ioctl(1, TIOCGWINSZ, &w_s)) {
w.y_size = w_s.ws_row;
w.x_size = w_s.ws_col;
}
// print terminal size and center it
gotoyx(w.y_size / 2, w.x_size / 2);
del_from_cursor(5);
printf("w.y_size: %d", w.y_size);
gotoyx((w.y_size / 2) + 1, w.x_size / 2);
del_from_cursor(5);
printf("w.x_size: %d", w.x_size);
// get key pressed by user in terminal & exit if <ESC> is pressed
if (kbget() == 0x001b) { break; }
}
printf("\033[2J\033[H\033[?1049l"); // clear screen & restore
return 0;
}
I have tried solving this using threads but I was unsuccessful so far.
I have modified the main.c file above by adding 2 functions (get_window_size & get_key):
(scr.h has the kbget() function in get_key() to change the terminal to canonical mode)
main.c:
#include "scr.h"
#include <sys/ioctl.h>
#include <pthread.h>
#define gotoyx(y, x) printf("\033[%d;%dH", (y), (x))
#define del_from_cursor(x) printf("\033[%dX", (x))
typedef struct {
int y_size;
int x_size;
} Window;
void *get_window_size(void *arg)
{
Window *w = (Window *)arg;
struct winsize w_s;
if (!ioctl(1, TIOCGWINSZ, &w_s)) {
w->y_size = w_s.ws_row;
w->x_size = w_s.ws_col;
}
pthread_exit(0);
}
void *get_key(void *arg)
{
int *key = (int *)arg;
free(arg);
*key = kbget();
int *entered_key = malloc(sizeof(*key));
*entered_key = *key;
pthread_exit(entered_key);
}
int main(void)
{
printf("\033[?1049h\033[2J\033[H");
Window w;
pthread_t tid[3];
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_create(&tid[0], &attr, get_window_size, &w);
int *c = malloc(sizeof(*c));
int *key_pressed;
while (1) {
// for initial size
pthread_join(tid[0], NULL);
// printing size to screen
gotoyx(w.y_size / 2, w.x_size / 2);
del_from_cursor(5);
printf("w.y_size: %d", w.y_size);
gotoyx((w.y_size / 2) + 1, w.x_size / 2);
del_from_cursor(5);
printf("w.x_size: %d", w.x_size);
// get window size
pthread_attr_t attr1;
pthread_attr_init(&attr1);
pthread_create(&tid[1], &attr1, get_window_size, &w);
// get key entered by user
pthread_attr_t attr2;
pthread_attr_init(&attr2);
pthread_create(&tid[2], &attr2, get_key, c);
pthread_join(tid[1], NULL);
pthread_join(tid[2], (void **)&key_pressed);
if (*key_pressed == 0x001b) {
break;
} else {
free(key_pressed);
}
}
if (key_pressed != NULL) {
free(key_pressed);
}
printf("\033[2J\033[H\033[?1049l");
return 0;
}
The scr.h file changes the terminal mode to non-canonical (the kbget() function above is called from here):
I don't think there's any problems in scr.h as it is taken from here (Move the cursor in a C program).
scr.h:
#ifndef SCR_H
#define SCR_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
struct termios term, oterm;
int getch(void)
{
int c = 0;
tcgetattr(STDIN_FILENO, &oterm);
memcpy(&term, &oterm, sizeof(term));
term.c_lflag &= ~(ICANON | ECHO);
term.c_cc[VMIN] = 1;
term.c_cc[VTIME] = 0;
tcsetattr(STDIN_FILENO, TCSANOW, &term);
c = getchar();
tcsetattr(STDIN_FILENO, TCSANOW, &oterm);
return c;
}
int kbhit(void)
{
int c = 0;
tcgetattr(STDIN_FILENO, &oterm);
memcpy(&term, &oterm, sizeof(term));
term.c_lflag &= ~(ICANON | ECHO);
term.c_cc[VMIN] = 0;
term.c_cc[VTIME] = 1;
tcsetattr(STDIN_FILENO, TCSANOW, &term);
c = getchar();
tcsetattr(STDIN_FILENO, TCSANOW, &oterm);
if (c != -1) { ungetc(c, stdin); }
return c != -1 ? 1 : 0;
}
int kbesc(void)
{
int c = 0;
if (!kbhit()) { return 0x001b; } // 0x001b is the <ESC> key
c = getch();
if (c == 0) { while (kbhit()) { getch(); } }
return c;
}
int kbget(void)
{
int c = getch();
return c == 0x001b ? kbesc() : c; // 0x001b is the <ESC> key
}
#endif // SCR_H
I also get errors Invalid write of size 4 in the code above with pthread while executing with valgrind:
gcc -Wall scr.h main.c -pthread -o main
valgrind -v --leak-check=yes ./main
I am aware of the existence of ncurses and pdcurses. I am only doing this as an exercise for myself.
UPDATE
I have changed my code to the following, unfortunately the ret variable never changes to -1:
#include "scr.h"
#include <errno.h>
#include <sys/ioctl.h>
#define gotoyx(y, x) printf("\033[%d;%dH", (y), (x))
#define del_from_cursor(x) printf("\033[%dX", (x))
typedef struct {
int y_size;
int x_size;
} Window;
static int sigwinch_arrived = 0;
void sigwinch_handler(int signum)
{ sigwinch_arrived = 1; }
void on_window_size_change(Window *w)
{
struct winsize w_s;
// evaluate terminal size
if (!ioctl(1, TIOCGWINSZ, &w_s)) {
w->y_size = w_s.ws_row;
w->x_size = w_s.ws_col;
}
// print terminal size in its center
gotoyx(w->y_size / 2, w->x_size / 2);
del_from_cursor(15);
printf("w.y_size: %d", w->y_size);
gotoyx((w->y_size / 2) + 1, w->x_size / 2);
del_from_cursor(15);
printf("w.x_size: %d", w->x_size);
}
int main(void)
{
printf("\033[?1049h\033[2J\033[H");
gotoyx(1, 10);
printf("Press <ESC> to stop program.");
gotoyx(2, 10);
printf("Resizing the terminal window does not 'automatically' update the size shown on screen");
Window w;
int ret;
while (1) {
// get key pressed by user in terminal & exit if <ESC> is pressed
ret = kbget();
gotoyx(10, 10);
del_from_cursor(8);
printf("ret: %d", ret);
if (ret == -1) {
if (errno == EAGAIN) {
if (sigwinch_arrived) {
sigwinch_arrived = 0;
on_window_size_change(&w);
}
}
} else if (ret == 0x001b) {
break;
}
}
printf("\033[2J\033[H\033[?1049l");
return 0;
}
Extension: as per this answer, if your ncurses was compiled with the --enable-sigwinch flag, it does the solution below automatically (if you did not override SIGWINCH before ncurses_init() yet). In this case, getch() (wgetch()) will simply return KEY_RESIZE if a resize event is happened.
If the size of your controlling character terminal changes, your process should get a SIGWINCH signal (window size change, signal 28 on Linux).
It can be sent by the kernel (if there is mode switch on a character desktop), or by the virtual terminal software (xterm, gnome-terminal, screen, etc).
If your process gets a signal, its blocking kernel calls, including getch(), stop with the -EAGAIN error number. It means, that the blocking call stopped before time due to an arrived signal.
Note, from a signal handler, you can't do too much (for example: no malloc()), and the best to do if you make the least possible. Typical signal handlers change a static, global variable, whose value is checked by the main program.
Untested example code:
static int sigwinch_arrived = 0;
// this is called from the signal handler - nothing complex is allowed here
void sigwinch_handler(int signum) {
sigwinch_arrived = 1;
}
// callback if there is a window size change
void on_window_size_change() {
...
}
// main program
...
while (1) { // your main event handler loop
int ret = getch();
if (ret == ERR) {
if (errno == EAGAIN) {
if (sigwinch_arrived) {
sigwinch_arrived = 0;
on_window_size_change();
}
}
}
...
}
Related
As an educational project, am I trying to write a simple terminal text editor. Below is a minimal example of my editor. My editor works fine when opening in the same terminal from which the program was run, however I would like to display the editor in a second terminal, leaving the original one free to print logging messages to. In the example below, the editor sucessfully opens in the second terminal, and the logging message about the number of rows and columns is printed in the original terminal. However, I can't work out how to get the editor to receive input from the second terminal, currently I need to close it by pressing ctrl+q in the orginal terminal. How can I get the editor to receive input from the second terminal?
I am compiling with gcc -Wall -Wextra -pedantic -std=c99 -g c_programs/open_alt_screen_in_other_term.c and running with ./a.out -tty /dev/pts/0
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>
#define ENTER_ALT_SCREEN "\033[?1049h"
#define EXIT_ALT_SCREEN "\033[?1049l"
#define MOVE_CURSOR_HOME "\033[H"
#define CTRL_KEY(k) ((k)&0x1f)
int UI_OUTPUT_FILENO = STDOUT_FILENO;
int UI_INPUT_FILENO = STDIN_FILENO;
struct termios orig_termios;
void disable_raw_mode();
void enable_raw_mode() {
tcgetattr(UI_INPUT_FILENO, &orig_termios);
atexit(disable_raw_mode);
struct termios raw = orig_termios;
raw.c_iflag &= ~(IXON | ICRNL);
raw.c_oflag &= ~(OPOST);
raw.c_lflag &= ~(ECHO | ICANON | ISIG | IEXTEN);
raw.c_iflag &= ~(BRKINT | INPCK | ISTRIP);
raw.c_cflag |= (CS8);
tcsetattr(UI_INPUT_FILENO, TCSAFLUSH, &raw);
}
void disable_raw_mode() {
tcsetattr(UI_INPUT_FILENO, TCSAFLUSH, &orig_termios);
write(UI_OUTPUT_FILENO, EXIT_ALT_SCREEN, 8);
}
int getWindowSize(int *rows, int *cols) {
struct winsize ws;
if (ioctl(UI_OUTPUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
return -1;
} else {
*cols = ws.ws_col;
*rows = ws.ws_row;
return 0;
}
}
int main(int argc, char **argv) {
FILE *lfp = NULL;
for (int a = 1; a < argc; a++) {
if (strcmp(argv[a], "-tty") == 0) {
lfp = fopen(argv[a + 1], "w");
UI_OUTPUT_FILENO = fileno(lfp);
// UI_INPUT_FILENO = UI_OUTPUT_FILENO;
}
}
enable_raw_mode();
write(UI_OUTPUT_FILENO, ENTER_ALT_SCREEN, 8);
write(UI_OUTPUT_FILENO, MOVE_CURSOR_HOME, 3);
int nrows, ncols;
if (getWindowSize(&nrows, &ncols) == -1) return EXIT_FAILURE;
// print logging to original terminal
printf("rows: %d, cols: %d\n", nrows, ncols);
char seq[10] = {0};
while (read(UI_INPUT_FILENO, &seq, 10) != 0) {
if (seq[0] == CTRL_KEY('q')) {
break;
} else if (strlen(seq) == 1) {
write(UI_OUTPUT_FILENO, seq, 1);
}
for (size_t j = 0; j < 10; j++) seq[j] = '\0';
}
// if I clode lfp, the other terminal doesn't exit alt screen
// fclose(lfp);
return 0;
}
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;
}
I wrote the following code to implement a periodic thread terminated when someone presses escape. The result is a periodic thread that continues also after i pressed escape.
Can you tell me where is the error please? The compiler compiles without any error and warning. Before reading the code jump to EDIT below.
#include <stdio.h>
#include <pthread.h>
#include <allegro.h>
#include <time.h>
void *task(void *p);
void time_add_ms(struct timespec *t, int ms);
int main()
{
int tret;
int a = 1;
allegro_init();
install_keyboard();
tret = pthread_create(&tid, NULL, task, (void*)&a);
pthread_join(tid, NULL);
printf("Thread1 returns %d\n", tret);
allegro_exit();
return 0;
}
/* Sommare quantità temporale espressa in ms al tempo nella struttura timespec */
void time_add_ms(struct timespec *t, int ms)
{
t->tv_sec += ms / 1000;
t->tv_nsec += (ms % 1000) * 1000000;
if (t->tv_nsec > 1000000000){
t->tv_nsec -= 1000000000;
t->tv_sec += 1;
}
}
/* Funzione da trasformare in task */
void *task(void *p)
{
struct timespec t;
int period = 100;
int *pi;
char scan = 0;
clock_gettime(CLOCK_MONOTONIC, &t);
time_add_ms(&t, period);
pi = (int *)p;
while (scan != KEY_ESC) {
if (keypressed()) scan = readkey() >> 8;
printf("This is TASK %d\n", *pi);
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &t, NULL);
time_add_ms(&t, period);
}
}
EDIT: maybe i found the problem, when i press escape, it prints the symbol ^, so the compiler or allegro doen't recognize the esc key.
This is not exactly finding the error, but I think it is what you want (more details here)
If you add following code to your main(), this will disable canonical mode, which will basically enable you to get characters immediately without waiting for a '\n' or EOF (or enter keypress in short) (already explained in the link above):
#include <termios.h>
#include <unistd.h>
int main(){
static struct termios oldt, newt;
tcgetattr( STDIN_FILENO, &oldt);
newt = oldt;
newt.c_lflag &= ~(ICANON);
tcsetattr( STDIN_FILENO, TCSANOW, &newt);
// rest of your code
}
And the thread loop here (I've changed it a little bit)
while (1) {
printf("This is TASK %d\n", *pi);
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &t, NULL);
time_add_ms(&t, period);
if(getchar()==27) break;
}
I have an event handling code that reads Linux's /dev/input/ for my touchpad and prints result on the basis of which button is pressed/released.
Although. as of now my code is waiting on a button press while running on terminal. My next step is to run this event handling thread along with another thread (not event based). If I continue handling event by reading input at terminal, I will not be able to execute other threads as a part of my main() as main() keeps on waiting for the button press:
int main(int argc, char** argv)
{
*Mouse event handling code here*
return 0;
}
Is there a different approach like reading interrupts instead? Or can I still take this approach and make amends in my code to make this work as a part of a thread (like can I make my thread to wait on these inputs as arguments)?
If you make the event device descriptors nonblocking (by opening them with the O_NONBLOCK flag), you can very easily use `poll() to wait until one of them has events you can read.
Consider the following example program, example.c:
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <linux/input.h>
#include <termios.h>
#include <poll.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
/* Maximum number of input sources, including the terminal. */
#ifndef MAX_INPUTS
#define MAX_INPUTS 32
#endif
/* Maximum wait for events, in milliseconds (1000 ms = 1 second). */
#ifndef INTERVAL_MS
#define INTERVAL_MS 100
#endif
int main(int argc, char *argv[])
{
unsigned char keys[16];
struct input_event event;
struct termios config, oldconfig;
struct pollfd src[MAX_INPUTS];
size_t srcs, i, done;
ssize_t n;
int arg, nsrcs;
if (!isatty(STDIN_FILENO)) {
fprintf(stderr, "Standard input is not a terminal.\n");
return EXIT_FAILURE;
}
/* Save old terminal configuration. */
if (tcgetattr(STDIN_FILENO, &oldconfig) == -1 ||
tcgetattr(STDIN_FILENO, &config) == -1) {
fprintf(stderr, "Cannot get terminal settings: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
/* Set new terminal configuration. */
config.c_iflag &= ~(IGNBRK | BRKINT | PARMRK);
config.c_lflag &= ~(ICANON | ISIG | ECHO | IEXTEN | TOSTOP);
config.c_cc[VMIN] = 0;
config.c_cc[VTIME] = 0;
config.c_cc[VSTART] = 0;
config.c_cc[VSTOP] = 0;
if (tcsetattr(STDIN_FILENO, TCSANOW, &config) == -1) {
const int saved_errno = errno;
tcsetattr(STDIN_FILENO, TCSANOW, &oldconfig);
fprintf(stderr, "Cannot set terminal settings: %s.\n", strerror(saved_errno));
return EXIT_FAILURE;
}
/* The very first input source is the terminal. */
src[0].fd = STDIN_FILENO;
src[0].events = POLLIN;
src[0].revents = 0;
srcs = 1;
/* Add input devices from command line. */
for (arg = 1; arg < argc; arg++) {
int fd;
fd = open(argv[arg], O_RDONLY | O_NOCTTY | O_NONBLOCK);
if (fd == -1) {
fprintf(stderr, "Skipping input device %s: %s.\n", argv[arg], strerror(errno));
continue;
}
if (srcs >= MAX_INPUTS) {
fprintf(stderr, "Too many event sources.\n");
return EXIT_FAILURE;
}
/* Optional: Grab input device, so only we receive its events. */
ioctl(fd, EVIOCGRAB, 1);
src[srcs].fd = fd;
src[srcs].events = POLLIN;
src[srcs].revents = 0;
srcs++;
}
printf("Ready. Press Q to exit.\n");
fflush(stdout);
done = 0;
while (!done) {
nsrcs = poll(src, srcs, INTERVAL_MS);
if (nsrcs == -1) {
if (errno == EINTR)
continue;
fprintf(stderr, "poll(): %s.\n", strerror(errno));
break;
}
/* Terminal is not an input source. */
if (src[0].revents & POLLIN) {
n = read(src[0].fd, keys, sizeof keys);
if (n > 0) {
for (i = 0; i < n; i++) {
if (keys[i] == 'q' || keys[i] == 'Q')
done = 1;
if (keys[i] >= 32 && keys[i] <= 126)
printf("Key '%c' = 0x%02x = %u pressed\n", keys[i], keys[i], keys[i]);
else
if (keys[i])
printf("Key '\\%03o' = 0x%02x = %u pressed\n", keys[i], keys[i], keys[i]);
else
printf("NUL key (0) pressed\n");
}
fflush(stdout);
}
src[0].revents = 0;
}
/* Check the other input sources. */
for (i = 1; i < srcs; i++) {
if (src[i].revents & POLLIN) {
while (1) {
n = read(src[i].fd, &event, sizeof event);
if (n != sizeof event)
break;
if (event.type == EV_KEY && event.code == BTN_LEFT) {
if (event.value > 0)
printf("Left mouse button pressed\n");
else
printf("Left mouse button released\n");
}
if (event.type == EV_KEY && event.code == BTN_RIGHT) {
if (event.value > 0)
printf("Right mouse button pressed\n");
else
printf("Right mouse button released\n");
}
}
fflush(stdout);
}
src[i].revents = 0;
}
}
/* Close input devices. */
for (i = 1; i < srcs; i++)
close(src[i].fd);
/* Restore terminal settings. */
tcsetattr(src[0].fd, TCSAFLUSH, &oldconfig);
printf("All done.\n");
return EXIT_SUCCESS;
}
Compile it using e.g.
gcc -Wall -O2 example.c -o example
and run it using e.g.
sudo ./example /dev/input/event5
where /dev/input/event5 is a mouse event device. Note that you can read /sys/class/input/event5/device/name to find out the name of the device (as far as the kernel knows it; these are the same names evtest shows when run as root).
If you are not sure, you can always run
for N in /sys/class/input/event*/device/name ; do
DEV="${N%%/device/name}" ; DEV="/dev/${DEV##/sys/class/}" ;
NAME="$(cat "$N" 2>/dev/null)" ;
printf "%s: %s\n" "$DEV" "$NAME" ;
done
in a Bash or Dash or a POSIX shell, to see what event devices you can try.
The example program above must be run from a terminal or console, because it also takes input from the terminal. It sets the terminal into nonblocking non-canonical mode, where it can receive individual keypresses. Do note that some keypresses, like cursor and function keys, are actually several characters long, beginning with an ESC (\033).
It is also common to split that input event loop into a separate thread. It is just a dozen or so lines more, but the "problem" then becomes how the separate thread informs the main (or other) threads that new input events/commands have arrived. The non-blocking poll() approach above is usually easier to implement in a very robust, straightforward manner.
My simple poll. the event routine attempt to get data from the two non blocking fds, one for mouse and one for keyboard. The event routine returns -1 or device busy when not ready, anything error below that is trapped by event. The if statement here tries fmd, mouse, first, then fkd next. A return less than one or zero means data not ready, the thread sleeps.
if( ( ( imd = event(fmd,&ie) ) <=0)&& ( ( ikd = event(fkd,&ie)) <= 0))
{
usleep(TIMEOUT);
continue;
}
I am aiming a simple code which looks like this:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <poll.h>
#include <linux/input.h>
int main()
{
int fd, bytes;
unsigned char data[3];
fd = open("/dev/input/mice", O_RDWR);
int left;
while(1)
{
bytes = read(fd, data, sizeof(data));
if(bytes > 0)
{
left = data[0] & 0x1;
right = data[0] & 0x2;
if(left==1){
goto reset_EVENT;
}
}
reset_EVENT:
printf("hey 1");
sleep(1);
printf("hey 2");
sleep(1);
printf("hey 3");
sleep(1);
}
return 0;
}
My goal here is to restart the print sequence from "hey 1" immediately when left mouse is clicked.
However, I am not able to achieve this without using "if" conditions before every sleep. a "goto" is not working for me across different functions too.
Can anyone suggest an optimal solution for this one please ?
You could achieve this with some kind of state machine: a function which has an
internal static state to know whick was the last hey printed
void print_event(int reset)
{
/* by default, print nothing */
static int state = 4;
/* if reset, restart counting */
if (reset)
state = 1;
/* if state under 4, display `hey` */
if (state <= 3)
printf("hey %d\n", state++);
}
int main()
{
int fd, bytes;
unsigned char data[3];
fd = open("/dev/input/mice", O_RDWR);
int left, right;
while(1)
{
/* by default, event is not reset */
int reset = 0;
bytes = read(fd, data, sizeof(data));
if(bytes > 0)
{
left = data[0] & 0x1;
right = data[0] & 0x2;
if(left==1){
/* left click: reset event*/
reset = 1;
}
}
/* ask function to print if needed */
print_event(reset);
sleep(1);
}
return 0;
}
EDIT
one problem in your loop is that you will wait a long time before reading new
value from fd.
You can use signal to achieve this:
continously reading input from mice, without sleeping
when left click is detected, arm an alarm signal to display event:
Thus, your code could be:
/* print event function: can be called from alarm signal handler
or directly from main function */
void print_event(int reset)
{
/* by default, print nothing */
static int state = 4;
/* if reset, restart counting */
if (reset)
state = 1;
/* if state under 4, display `hey` */
if (state <= 3)
{
printf("hey %d", state++);
/* if all `hey` have not been printed, prepare the next */
alarm(1);
}
}
/* alarm signal receiver*/
void handle(int sig) {
/* print event without reset */
print_event(0);
}
int main()
{
int fd, bytes;
unsigned char data[3];
fd = open("/dev/input/mice", O_RDWR);
int left;
/* tell what function is to be called when alarm signal is raised */
signal(SIGALRM, handle);
/* if you want to display one sequence before the first click, uncomment the following line */
/* print_event(1); */
while(1)
{
bytes = read(fd, data, sizeof(data));
if(bytes > 0)
{
left = data[0] & 0x1;
if(left==1){
/* left click: reset event*/
print_event(1);
}
}
}
return 0;
}