Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: 71d09f6155
Fetching contributors…

Cannot retrieve contributors at this time

executable file 265 lines (209 sloc) 6.874 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
#!/usr/bin/perl
our $APP = 'clipbored';
our $VERSION = '1.2.4';
use strict;
use Getopt::Long;
use Pod::Usage;
use File::Path 'mkpath';


my $xsel_log = clip_location();
my $pidfile = '/tmp/clipbored.pid';

my($opt_no_daemon, $opt_x_buffer) = (undef, lc($ENV{CLIPBORED_X_SELECTION}));
if(!defined($opt_x_buffer)) {
  $opt_x_buffer = 'primary';
}

my($opt_no_daemon) = undef;
GetOptions(
 'no-daemon' => \$opt_no_daemon,
 'kill' => \&killkid,
 'clear' => sub {
   if(-e $xsel_log) {
     open (my $fh, '>', $xsel_log) or die("Could not open $xsel_log: $!");
     close($fh);
   }
   print "$xsel_log cleared\n";
   exit(0);
   },
 'last:i' => \&lastlog,
 'help' => sub {pod2usage(-verbose => 1); exit },
 'man' => sub {pod2usage(-verbose => 3); exit },
 'version' => sub { printf("%s v%s\n", $APP, $VERSION); exit },
);

if(-f $pidfile) {
  print "clipbored is already running\n";
  exit 1;
}

sync_cb();

sub lastlog {
  shift;
  my $wayback = shift;
  $wayback = 25 unless(defined($wayback));

  if($wayback < 1) {
    $wayback = 25;
  }
  open(my $fh, '<', $xsel_log) or die("Could not open $xsel_log: $!");
  my @records = <$fh>;
  close($fh);

  if(scalar(@records) < $wayback) {
    $wayback = scalar(@records);
  }

  my $i = 0;
  for(@records[scalar(@records) - $wayback .. scalar(@records) - 1]) {
    printf("\e[1m%2d\e[0m %s", $i, $_);
    $i++;
  }
  #FIXME collected since DATE
  print "\e[1m" . scalar(@records) . "\e[0m selections collected \n";
  exit(0);
}


sub sync_cb {
  daemonize() unless(defined($opt_no_daemon));
  while(1) {
    my %selections = ();
    chomp(my $current_selection = `/usr/bin/xclip -o -selection $opt_x_buffer`);
    if(defined($current_selection)) {
      open(my $r_xsel, '<', $xsel_log) or die("Cant open $xsel_log: $!");
      chomp(my @selections = <$r_xsel>);
      close($r_xsel);

      $current_selection =~ s/\n/ /g; # newline hassle

      map { $selections{$_} = undef } @selections;

      if(exists($selections{$current_selection})) {
        # DUPE
      }
      else {
        open(my $a_xsel, '>>', $xsel_log) or die("Cant open $xsel_log: $!");
        print $a_xsel $current_selection, "\n";
        close($a_xsel);
        print $current_selection, "\n" if(defined($opt_no_daemon));
      }
    }
    sleep 2;
  }
}

sub killkid {
  open(my $fh, '<', $pidfile) or print "clipbored is not running\n" and exit(1);
  my $target = <$fh>;
  close($fh);

  if(kill(9, $target)) {
    print "clipbored with PID $target terminated\n";
  }
  else {
    print "Could not kill $target: $!";
  }
  exit(0);
}

sub daemonize {
  use POSIX 'setsid';
  my $PID = fork();
  exit(0) if($PID); #parent
  exit(1) if(!defined($PID)); # out of resources

  setsid();
  $PID = fork();
  exit(1) if(!defined($PID));

  if($PID) { # parent
    waitpid($PID, 0);
    unlink($pidfile); # remove the lock when child have died
    exit(0);
  }
  elsif($PID == 0) { # child
    open(my $fh, '>', $pidfile) or die("Cant open $pidfile: $!");
    print $fh $$;
    close($fh);
    open(STDOUT, '>', '/dev/null');
    open(STDERR, '>', '/dev/null');
    open(STDIN, '<', '/dev/null');
  }
}

sub clip_location {
  # File::Path exports a make_path function that would allow us to emulate
  # mkdir -p behaviour. People running Perl < 5.10 didnt like that though.
  my $dir = undef;
  if(!defined($ENV{XDG_DATA_HOME}) or(!-d $ENV{XDG_DATA_HOME})) {
    $dir = "$ENV{HOME}/.local";
  }
  else {
    $dir = $ENV{XDG_DATA_HOME};
  }
  if(!-d $dir) {
    mkpath($dir);
  }
  $dir .= "/clipbored";
  if(!-d $dir) {
    mkpath($dir);
  }
  $dir .= '/clips';
  open(my $fh, '>>', $dir) or die($!);
  close($fh);
  return($dir);
}

__END__

=pod

=head1 NAME

clipbored - continuously collects all selections in Xorg's clipboard buffers

=head1 SYNOPSIS

clipbored [OPTIONS]

=head1 DESCRIPTION

B<clipbored> is a daemon that continuously grabs all non-duplicate selections
in the X.org clipboard buffers and writes them to a plaintext history file for
later use.

There are several scripts distributed with clipbored that'll use the history
file for different purposes.

=head2 Scripts

dmenurl - launch dmenu with all previously yanked URLs for you to select
from.

dmenuclip - launch dmenu listing all previously clipboarded content

=head1 OPTIONS

-l, --last show the n latest additions
-c, --clear clear all history
-n, --no-daemon do not detach from the shell
-k, --kill kill a running clipbored session
-h, --help show this help
-m, --man display the manual
-v, --version show version info

=head1 ENVIRONMENT

The history file location is $XDG_DATA_HOME/clipbored/clips

The X selection to use can be specified by setting the B<CLIPBORED_X_SELECTION>
environment variable.

If unset, or set to I<primary> , text is grabbed from the B<XA_PRIMARY> buffer.
When text is selected with the mouse, or piped through xclip/xsel with zero
arguments, it ends up here. This is most likely what you want.

If set to I<clipboard>, text is grabbed from the B<XA_CLIPBOARD> buffer. Data
ends up in this buffer when an explicit action is taken to cut/copy text; used
in many GUI environments.

To the best of my knowledge, the I<secondary> buffer is rarely used at all.

The helper scripts can read properties from environment variables.
These are recognized:

CLIPBORED_DMENU_LISTMODE regular/vertical
CLIPBORED_DMENU_NORMAL_FG foreground color in HEX
CLIPBORED_DMENU_NORMAL_BG background color in HEX
CLIPBORED_DMENU_SELECT_FG selected item background color in HEX
CLIPBORED_DMENU_SELECT_BG selected item foreground color in HEX
CLIPBORED_DMENU_FONT font that will be used
CLIPBORED_DMENU_LINES how many lines that will be shown in vertical mode
CLIPBORED_X_SELECTION X buffer to use: primary, secondary, clipboard

=head1 AUTHOR

\ \ | / /
\ \ - /
\ | /
(O O)
( < )
(-=-)

Magnus Woldrich
CPAN ID: WOLDRICH
magnus@trapd00r.se
http://japh.se

=head1 CONTRIBUTORS

Markus Weimar suggested we should be able to pick the clipboard buffer to use.
Since I very rarely use any GUI applications, I wasn't aware of the fact that
when CTRL+C/CTRL+V etc is used, it goes into the XA_CLIPBOARD buffer instead of
the XA_PRIMARY, probably rendering clipbored somewhat useless to the users using
these types of applications. :)

=head1 REPORTING BUGS

Report bugs to L<trapd00r@trapd00r.se> or L<use the issue tracker|http://github.com/trapd00r/clipbored/issues>.

clipbored home page: L<http://github.com/trapd00r/clipbored/>

=head1 COPYRIGHT

Copyright 2010, 2011 the B<clipbored>s L</AUTHOR> and L</CONTRIBUTORS> as listed
above.

=head1 LICENSE

This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.

=cut
Something went wrong with that request. Please try again.