Simple capturing of keyboard input without interruption


 
Thread Tools Search this Thread
Top Forums Programming Simple capturing of keyboard input without interruption
# 1  
Old 03-04-2018
Simple capturing of keyboard input without interruption

I would like to make a function or command that checks for keyboard input without interrupting the program and exits the program when a key is pressed (perhaps the 'q' key).

The program below monitors and prints/executes commands upon a change in primary (mouse selection) or clipboard buffer. If someone wanted to look up any mouse selected word in a dictionary, they could do this by running:
Code:
clcp -e 'midori http://www.webster-dictionary.org/definition/%clip%'

Where '%clip%' is the word to be replaced by the buffer text and the '-e' parameter tells the program to execute and not just print to screen.

This part of the program is working. The issue is with the keyboard input. Upon pressing a key, it will exit and then run the key pressed as a command. If I pressed <ENTER> to exit it would drop down a line in the console; there would be an extra '$' in the console. If I pressed 'q', a 'q' would be echoed in the next console line. I do not want this. Also, I haven't figured out how to exit when only a certain key is pressed.

I've tried using ncurses, and I can show the code if asked. Curses seems to want to do its own thing and not cooperate with console program outputs; it will shift the next line to the right of where it left off on the line above causing a horizontal splash of sorts and distorting command outputs such as 'ls' and 'cat'. The program I have made uses the 'system' command to run commands, and so I don't want ncurses getting in the way.

Here is the program:
Code:
// This program moniters the clipboard / primary (mouse selection) buffers
// and prints text or runs a command on change.
//
// The goals of this project:
//
//	1. < 100 lines code
//	2. Simple & elegant coding
//	3. Fast & efficient execution.
//
//		"Do one thing,
//		 and do it well."
//
//		-Linux Credo
//
// Credits:
// https://stackoverflow.com/questions/27378318/c-get-string-from-clipboard-on-linux
// https://cboard.cprogramming.com/c-programming/63166-kbhit-linux.html
// https://www.unix.com/
//
// Compile with:
// $ g++ -O -Wall clcp.cpp -o clcp -lX11 -std=c++17
//
// Print clipboard buffer text to screen and wait 1000 milliseconds
// to check again:
// $ clcp -c 1000
//
// Run output of clipboard buffer as a command then exit immediately
// (a '0' tells the program to exit and not wait and grab more input):
// $ clcp -c -e 'midori https://duckduckgo.com/?q=%clip%' 0
//
// Where the command ran would be (if the word 'debian' were to have
// been selected by the mouse):
// $ midori https://duckduckgo.com/?q=debian
//
// Print to screen (default) what is mouse selected and check buffers
// every 500ms (default) for a change:
// $ clcp
//
// Use with UTF-8 instead of regular string. This is helpful when using
// characters with macrons and other characters of this kind:
// $ clcp -u
//
// Press any key to exit program.
//
// github: https://gist.github.com/bathtime/39502e6ae6524a4fc37cb55f4d5459fa
//
// Feel free to fork or make contributions, but if you contribute, and I
// very much welcome it :), please remove a line of code for every
// line you add by making the code more efficient; the program must not
// exceed 200 code lines (or the world explodes).
//

#include<experimental/string_view>
#include<algorithm>
#include<string.h>
#include<iostream>
#include<limits.h>
#include<X11/Xlib.h>
#include<chrono>
#include<thread>
#include<termios.h>
#include<unistd.h>
#include<sys/time.h>
#include<sys/types.h>
#include<stdio.h>
#include<termios.h>
#include<unistd.h>
#include<fcntl.h>

// Function to capture clipboard buffers
std::string PrintSelection(Display *display, Window & window, const char *bufname, const char *fmtname, std::string text)
{
	char *result;
	unsigned long ressize, restail;
	int resbits;
	Atom bufid	= XInternAtom(display, bufname, False),
	fmtid		= XInternAtom(display, fmtname, False),
	propid 		= XInternAtom(display, "XSEL_DATA", False),
	incrid		= XInternAtom(display, "INCR", False);
	XEvent event;

	XConvertSelection(display, bufid, fmtid, propid, window, CurrentTime);

	do
		XNextEvent(display, &event);
	while (event.type != SelectionNotify || event.xselection.selection != bufid);

	if (event.xselection.property)
	{
		XGetWindowProperty(display, window, propid, 0, LONG_MAX/4, False, AnyPropertyType, &fmtid, &resbits, &ressize, &restail, (unsigned char**)&result);

		if (fmtid == incrid)
			printf("Buffer is too large and INCR reading is not implemented yet.\n");
		else {

			std::string clpWrd = "%clip%";

			// Replace clpWrd with buffer contents
			std::size_t pos = text.find(clpWrd);

			if (pos != std::string::npos)
                        	text = text.substr(0, pos) + result + text.substr(pos + clpWrd.length(), text.length());
                	else
				text += result;
		}

		XFree(result);

		return text;

	} else{	// request failed, e.g. owner can't convert to the target format
		std::cout << "No buffered content. Please fill buffer." << std::endl;
		return "";
	}
}

// Capture keyboard input

void changemode(int);
int  kbhit(void);

void changemode(int dir)
{
	static struct termios oldt, newt;

	if ( dir == 1 )
	{
		tcgetattr( STDIN_FILENO, &oldt);
		newt = oldt;
		newt.c_lflag &= ~( ICANON | ECHO );
		tcsetattr( STDIN_FILENO, TCSANOW, &newt);
	} else
		tcsetattr( STDIN_FILENO, TCSANOW, &oldt);
}

// Keyboard input
int kbhit (void)
{
	struct timeval tv;
	fd_set rdfs;

	tv.tv_sec = 0;
	tv.tv_usec = 0;

	FD_ZERO(&rdfs);
	FD_SET (STDIN_FILENO, &rdfs);

	select(STDIN_FILENO+1, &rdfs, NULL, NULL, &tv);

	return FD_ISSET(STDIN_FILENO, &rdfs);
}

using namespace std;

int main(int argc, char* argv[]) {

	// Set defaults if they are not entered as parameters
	std::string line 	= "";		// Text to be printed/manipulated
	std::string tmpStr	= "";
	const char * strType	= "STRING";	// STRING  or UTF8_STRING
	const char * clpType	= "PRIMARY";	// PRIMARY or CLIPBOARD
        bool isExec             = 0;            // Default to print text, not execute
        long milSec             = 500;          // Miliseconds for thread sleep

	// Sort out parameters and set variables
        for(int pNum = 1; pNum < argc; pNum++) {

		tmpStr = argv[pNum];

		// First check if it is a number. Use new efficient string_view func :)
		if (!std::experimental::string_view(argv[pNum]).empty() && std::all_of(tmpStr.begin(), tmpStr.end(), ::isdigit))
			milSec	= stoi(argv[pNum]);
		else if (std::experimental::string_view(argv[pNum]) == "-c")
			clpType = "CLIPBOARD";
		else if (std::experimental::string_view(argv[pNum]) == "-u")
                        strType = "UTF8_STRING";
		else if (std::experimental::string_view(argv[pNum]) == "-e")
			isExec 	= 1;
		else	// Just append other stuff to text (quoted or unquated)
			line += argv[pNum];
	}

	// Buffer capture variables
	Display *display	= XOpenDisplay(NULL);
	unsigned long color	= BlackPixel(display, DefaultScreen(display));
	Window window		= XCreateSimpleWindow(display, DefaultRootWindow(display), 0,0, 1,1, 0, color, color);

	changemode(1);		// keyboard input

	std::string lastClip;
        std::chrono::milliseconds timespan(milSec);

	// Let's roll whilst no key is pressed!
	while (!kbhit()){

		// Gather all the output info
		std::string result = PrintSelection(display, window, clpType, strType, line);

		if (result != lastClip)
		{
			lastClip = result;

		        if (isExec)			// Execute or print to screen?
				system(result.c_str());
			else
        		        std::cout << result << std::endl;

			if (milSec == 0) return 0;	// Just run once and exit.
		}

		// Sleep in an efficient manner
		std::this_thread::sleep_for(timespan);
	}

	// Buffer capture shutting down commands
	XDestroyWindow(display, window);
	XCloseDisplay(display);

	changemode(1);		// Keyboard input. Change back to regular mode.

	return 0;
}

I've tried to tidy up the code and make as simple and readible as possible; most buffer functions/commands and key input functions/commands have been marked to aid this.

For the love of Jupiter, if there is any way to simply exit a program, I'd gladly welcome it. I've tried with two different custom versions of key input, both three times from scratch and ncurses three times from scratch, all with less than satisfying results.

Thank you. Smilie
# 2  
Old 03-06-2018
Overview:
I'm not going to figure out all of the above wall of code. This example is how MS windows and X work. A simple, fairly stupid, event (message) pump that does nothing but read stdin, then send it off to a pipe. The little one I call "tiny" your code wall is "big" and it needs to dup() stdin on startup.

What you will do is to steal a tiny bit of windowing architecture - as in a message pump:
interpose a tiny program that loops and reads each character from stdin and then sends everything it gets from stdin to a pipe -except the <return> key. It exits or whatever you need on the return key.

Start the tiny program, have it fork your large program as a child that reads input from a pipe. Big's code can live in the same physical code that tiny lives in. Call fork() on the "big" entry point.

Tiny reads everything and simply passes it on, except in your case tiny exits when the ASCII 13 character (\n) is read. Tiny has almost zero smarts, just calls signal(), read(), wait() and pipe().

If tiny wants to quit: signal big, call wait() on big, then exit().
If big wants to quit: signal tiny, then exit() Tiny's signal handler gets the signal and exits.

If you really get stuck, post your attempt. If executable code lines in the tiny program code exceeds circa 50 lines or so your logic is probably too complex. Add bells and whistles after it works.
This User Gave Thanks to jim mcnamara For This Post:
# 3  
Old 03-07-2018
Quote:
Originally Posted by jim mcnamara
Overview:
I'm not going to figure out all of the above wall of code. This example is how MS windows and X work. A simple, fairly stupid, event (message) pump that does nothing but read stdin, then send it off to a pipe. The little one I call "tiny" your code wall is "big" and it needs to dup() stdin on startup.

What you will do is to steal a tiny bit of windowing architecture - as in a message pump:
interpose a tiny program that loops and reads each character from stdin and then sends everything it gets from stdin to a pipe -except the <return> key. It exits or whatever you need on the return key.

Start the tiny program, have it fork your large program as a child that reads input from a pipe. Big's code can live in the same physical code that tiny lives in. Call fork() on the "big" entry point.

Tiny reads everything and simply passes it on, except in your case tiny exits when the ASCII 13 character (\n) is read. Tiny has almost zero smarts, just calls signal(), read(), wait() and pipe().

If tiny wants to quit: signal big, call wait() on big, then exit().
If big wants to quit: signal tiny, then exit() Tiny's signal handler gets the signal and exits.

If you really get stuck, post your attempt. If executable code lines in the tiny program code exceeds circa 50 lines or so your logic is probably too complex. Add bells and whistles after it works.
Much of these terms are new to me. On the weekend, I'll search those terms and do a little research and see what comes out of it. Hopefully this does not get too complex. Smilie

Thank you for writeup. Smilie
# 4  
Old 03-07-2018
Oh. I assumed too much. If you are going to code in Unix, please consider reading/referring to one of these books - the calls I mentioned are all explained with examples:

Michael Kerrisk 'The Linux Programming Interface: A Linux and UNIX System Programming Handbook'

Stevens & Rago 'Advanced Programming in the UNIX Environment, 3rd Edition'
This User Gave Thanks to jim mcnamara For This Post:
# 5  
Old 03-07-2018
Quote:
Originally Posted by jim mcnamara
Oh. I assumed too much. If you are going to code in Unix, please consider reading/referring to one of these books - the calls I mentioned are all explained with examples:

Michael Kerrisk 'The Linux Programming Interface: A Linux and UNIX System Programming Handbook'

Stevens & Rago 'Advanced Programming in the UNIX Environment, 3rd Edition'
Thank you. I had a quick look on Amazon to preview these books, and they look like quite solid resources. I'll give it a 3 day attempt on the weekend and decide if the venture is worth it for me.

If I do come up with something, I'll post it and make sure to just have it as a small program which only has what is required and no other stuff. Smilie
Login or Register to Ask a Question

Previous Thread | Next Thread

10 More Discussions You Might Find Interesting

1. Shell Programming and Scripting

Read input from Keyboard, do not proceed if no input

Hi, I am working on a script, which requests users to enter input. Ex: read -p "Please enter your email id:" email I don't want users skipping this entry, this has to be mandatory.I dont want to proceed without input. I can do a check if variable $email is empty and proceed if not.But, i... (7 Replies)
Discussion started by: aravindadla
7 Replies

2. Shell Programming and Scripting

How to get input from keyboard with watch?

Hello, i`m trying to create an network monitoring script and i dont know how to make affect that script by pressing an key from keyboard and that script runs not in while or for or any other loop, but with bash command watch for example: i have created an file (for example check) with content... (0 Replies)
Discussion started by: bacarrdy
0 Replies

3. Shell Programming and Scripting

Capturing multiple values from user input

Hello, I need to capture multiple values from user input and do not know how to do it. I'm writing a script to delete old files, but want to give the option to keep some by asking the user. This is what my output looks like... Old files to be deleted... 1 file1 2 file2 Then this bit of... (3 Replies)
Discussion started by: jrymer
3 Replies

4. Shell Programming and Scripting

sed execution with input from keyboard

> sed '' Hello hi Hello output How hi output ^D How > sed should take each line as input, process and output the result. In the above scenario the input is passed from keyboard and the output of 'Hello' as you can see is displayed on the screen after 'hi' is passed as input but not as... (1 Reply)
Discussion started by: loggedin.ksh
1 Replies

5. Shell Programming and Scripting

Capturing script output and input to log

Hi Is there a way that I can capture a shell script (both output and input) to a log file where I can analyze it? Thanks (6 Replies)
Discussion started by: nimo
6 Replies

6. Shell Programming and Scripting

Keyboard input question

How would I change up a script that currently has something like: bash script echo what's 1 2 3 4? then using read 1 2 3 4 I type 1 2 3 4. so in the script i can do stuff like echo $1 $2 $3 $4 and such... i was just doing echo "1 2 3 4"|bash script But was wondering how could I... (5 Replies)
Discussion started by: biopulse
5 Replies

7. UNIX for Dummies Questions & Answers

Capturing Input Parameters on Shell Script

i have this basic line of code that doesn't work. i simply want to get the input parameter strings but when the script is run it appears that the first parameter is assigning the value to the second parameter. #!/bin/sh pdir=`pwd` p1=$1 p2=$2 echo "directory: $pdir\n" echo "parameter... (2 Replies)
Discussion started by: wtolentino
2 Replies

8. Programming

Keyboard Input

Does anyone know how do you determine the user idle time of STDIN in order to log the user out for being idle too long. I would like to write a c program to do this but I it is not clear upon how to determine idle time from keyboard input. I have found that the "who.c" source file uses the last... (4 Replies)
Discussion started by: cpaquette
4 Replies

9. Programming

Detecting Keyboard Input without return

Hi, I need a way to detect the up and down arrow key inputs for my program. I do not want to wait for the return key to be entered(so that rules out getch() and family). Also I need to process several of these inputs in parallel, by servicing each request with a thread. Is that possible? ... (4 Replies)
Discussion started by: ravneetd
4 Replies

10. Shell Programming and Scripting

suppressing keyboard input

Setup Info: This User Id and Password mention below are being used with the ISQL command to connect to a sybase database so they are likely to not be the same as those that were signed on from the session. Situation: Using a korn shell, the shell prompts for a User Id and Password. During the... (1 Reply)
Discussion started by: anthreedhr
1 Replies
Login or Register to Ask a Question