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:
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:
// 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.