Unix/Linux Go Back    


Programming Post questions about C, C++, Java, SQL, and other programming languages here.

Simple capturing of keyboard input without interruption

Programming


Tags
buffer, curses, input, keyboard, ncurses

Reply    
 
Thread Tools Search this Thread Display Modes
    #1  
Old Unix and Linux 03-04-2018   -   Original Discussion by bedtime
bedtime's Unix or Linux Image
bedtime bedtime is offline
Registered User
 
Join Date: Feb 2018
Last Activity: 3 April 2018, 4:11 PM EDT
Posts: 30
Thanks: 23
Thanked 3 Times in 3 Posts
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. Linux
Sponsored Links
    #2  
Old Unix and Linux 03-06-2018   -   Original Discussion by bedtime
jim mcnamara's Unix or Linux Image
jim mcnamara jim mcnamara is offline Forum Staff  
...@...
 
Join Date: Feb 2004
Last Activity: 26 May 2018, 5:40 PM EDT
Location: NM
Posts: 11,393
Thanks: 616
Thanked 1,158 Times in 1,067 Posts
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.
The Following User Says Thank You to jim mcnamara For This Useful Post:
bedtime (03-07-2018)
Sponsored Links
    #3  
Old Unix and Linux 03-07-2018   -   Original Discussion by bedtime
bedtime's Unix or Linux Image
bedtime bedtime is offline
Registered User
 
Join Date: Feb 2018
Last Activity: 3 April 2018, 4:11 PM EDT
Posts: 30
Thanks: 23
Thanked 3 Times in 3 Posts
Quote:
Originally Posted by jim mcnamara View Post
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. Linux

Thank you for writeup. Linux
    #4  
Old Unix and Linux 03-07-2018   -   Original Discussion by bedtime
jim mcnamara's Unix or Linux Image
jim mcnamara jim mcnamara is offline Forum Staff  
...@...
 
Join Date: Feb 2004
Last Activity: 26 May 2018, 5:40 PM EDT
Location: NM
Posts: 11,393
Thanks: 616
Thanked 1,158 Times in 1,067 Posts
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'
The Following User Says Thank You to jim mcnamara For This Useful Post:
bedtime (03-07-2018)
Sponsored Links
    #5  
Old Unix and Linux 03-07-2018   -   Original Discussion by bedtime
bedtime's Unix or Linux Image
bedtime bedtime is offline
Registered User
 
Join Date: Feb 2018
Last Activity: 3 April 2018, 4:11 PM EDT
Posts: 30
Thanks: 23
Thanked 3 Times in 3 Posts
Quote:
Originally Posted by jim mcnamara View Post
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. Linux
Sponsored Links
Reply

Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes

Linux More UNIX and Linux Forum Topics You Might Find Helpful
Thread Thread Starter Forum Replies Last Post
Read input from Keyboard, do not proceed if no input aravindadla Shell Programming and Scripting 7 02-26-2016 01:24 PM
How to get input from keyboard with watch? bacarrdy Shell Programming and Scripting 0 04-01-2015 08:11 PM
Keyboard input question biopulse Shell Programming and Scripting 5 11-29-2010 12:41 AM
Keyboard Input cpaquette Programming 4 04-07-2008 01:44 PM
suppressing keyboard input anthreedhr Shell Programming and Scripting 1 10-21-2004 11:05 AM



All times are GMT -4. The time now is 09:12 PM.