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;
}