#!/usr/bin/perl # gClip, Clipboard Manager (stable edition) : raimo 20090803 @@@@ # license: http://creativecommons.org/licenses/by-nc-sa/1.0/ # # REQUIRED LIBS: libgtk2-perl libgtk2-trayicon-perl # # INSTALL: # sudo apt-get install libgtk2-perl libgtk2-trayicon-perl # wget -O ~/gclip.txt http://gclip.uhvo.org/sec/gclip.txt # sudo cp -i ~/gclip.txt /usr/bin/gclip # sudo chmod 755 /usr/bin/gclip # # INSTALL MENU ITEM for Xfce4 and Gnome (Applications -> Accessories -> gClip) # wget -O ~/gclip.desktop http://gclip.uhvo.org/sec/gclip.desktop # sudo cp -i ~/gclip.desktop /usr/share/applications/gclip.desktop # # RUN 1st: gclip --help # RUN 2nd: gclip & # # !! Notification Area Applet or similar tray area must be in running !! # Settings file (~/.gclip/gcliprc) will be auto-created in first start. # Right-click the tray-icon and select Preferences from menu .. # # Primary selection can be stored with tray-icon middle click or # menus 3rd row left-click. # # For help and bug fixes look bottom of this file. # # # ############ USER DEFAULT SETTINGS IN ~/.gclip/gcliprc FILE ############ # These values will be restored with Preferences -> Restore defaults -selection print "\ngClip -> startting, wait a moment...\n"; #0119 our $autostart = 1; # 0/1 starts within window manager (works with Xfce4 and Gnome and ?) ## changed to 1 #0119 change back to 0 if causes something odd. our $disabled = 0; # 0/1 sniffer disabled, --disable / --enable overrides our $history = 1; # 0/1 remove/keep data when ending our $tips = 0; # 0/1 show tooltips # new in 0116.1 changed to 0 in #0119 our $silent = 2; # message viewing level > 4 almost silent our $limit = 20; # items in menu (data-files alive), default 20 our $clip = 30; # menu-item length marks, default 30 our $primary = 1; # 0/1 use primary buffer, BETA def 1 now 0115.1 our $browser = "firefox"; # Your favourite www-browser, will be opera I quess ;) our $editor = "gedit"; # Your favourite text-file editor our $decode = 1; # decode for umlauts, set 0 if english our @openwith = ("leafpad", "geany", "bluefish", "firefox", "gqview", "gimp"); ######## TRANSLATE MESSAGES HERE OR IN ~/.gclip/lang.lng FILE ########## # Copy THIS to ~/.gclip/lang.lng file # -- clip-- our %lang = (prog0 => "Clipboard manager started.", # tray message prog1 => "Can't start, already running.", # dialog message prog2 => "Remove clipboard data file?", # dialog message prog3 => "Clipboard manager stopped", # tray message prog5 => "", # menu label prog6 => "Locked items", # menu label msg01 => "Can't remove file", # dialog message msg02 => "Clipboard manager not running", # tray message msg03 => "History clearing .. ", # dialog message msg04 => "Clipboard is empty.", # tray message msg05 => "Item is locked, can't remove.", # dialog message msg06 => "Primary selection saved", # tray message main1 => "preferences", # word in window label main2 => "Items visible", # main window label main3 => "Item length", # main window label main5 => "Browser", # main window label main6 => "Editor", # main window label main7 => "Save clipboard history", # main window CB label main8 => "Autostart", # main window CB label main9 => "Menu items", # main window label mainA => "Programs", # main window label mainB => "General", # main window label mainC => "Open target with..", # main window label + menuR00 mainD => "Restore defaults", # main window button mainE => "Use Primary buffer", # main window button mainF => "Show tooltips", # main window button subm1 => "Open URL", # submenu label subm2 => "Clipboard", # submenu label subm3 => "View item", # submenu label subm4 => "Lock item", # submenu label subm5 => "Remove item", # submenu label subm6 => "Primary", # submenu label clip1 => "Removing ALL stored clipboard data..\nAre You sure?", # clip2 => "Some files can't be removed.", # warning message warn1 => "WARNING!\nYou are root!", #error message warn2 => "Please, install program properly.", # error message warn3 => "Can't write to ", # error message warn4 => "History clearing failed!" # warning message ); # -- clip-- ################### DO NOT EDIT BELOW OF THIS LINE ##################### my $license = "CC BY-NC-SA\nhttp://creativecommons.org/licenses/by-nc-sa/1.0/"; use strict; use Gtk2::TrayIcon; # libgtk2-trayicon-perl use Gtk2 "-init"; # libgtk2-perl use Encode; # for UTF-8 bugs sub deco; my $home = $ENV{HOME}; my $user = $ENV{USER}; my $dir = "$home/.gclip"; # main directory my $set1 = "$dir/gcliprc"; # main settings file my $set2 = "$dir/gclip-gtkrc"; # GtkRc settings file my $lng = "$dir/lang.lng"; # translation file my $tmpfile = "$dir/temp.tmp"; # temp-file my $name = $0; # gClip own name $name =~ s/^.*\///; print "checked name: $name"; #0119 my $vers = 20090803; our $curvers; # for automatic update, 0115.1 supposed to 0119 too. our $style; # for gtkrc my ($ebox, $mainA, $msgid, $currentC, $currentP, $primM, $enable); my $lock = 0; # do not change (0) my $req = 2; # do not change (2) my $int = 2; # tray message delay, seconds exit print &help if grep/^-h$|--help$|^-v$|^--version$/, @ARGV; ## RUN ONLY ONCE (not everyone has pgrep) sleep(2); #0119 my $self = grep/$name\n$/, `ps -A`; exit &dialog("warning", "ok", %lang -> {prog1}) if $self > 1; #0119 print "\nautostart OK"; #0119 $editor = &get_editor if !-x "/usr/bin/$editor" && !-x $editor; unlink $set1 if -f $set1 && grep/^--reset-all$/, @ARGV; &install; # installation and settings check exit print "\nReset done, restart $name\n" if grep/^--reset-all$/, @ARGV; # testing if getting those ext files our $lang_test = "not in use"; our $set_test = $lang_test; our $gtk_test = $lang_test; do $lng if -f $lng; # strings file do $set1 if -f $set1; # user settings file do $set2 if -f $set2; # Gtkrc settings file my @managers = &get_managers; @openwith = &get_openwith; print "\ngClip running...\n"; #0119 print "\nlang.lng: $lang_test\ngcliprc: $set_test\ngclip-gtkrc: $gtk_test\n"; ### This IS only for dev-time, 0115.1, stable remove -> NOT removing 0118, 0119 ### if ($curvers ne $vers && (!grep/^--force$/, @ARGV)){ my $msg = "Please, update lang.lng file too | NO lng-update reason for new 20090803." if -f "$dir/lang.lng"; #0119 &dialog("info", "ok", "BETA-version changed.\n" . "Restoring default settings now, sorry!\n$msg"); print "Upadating settings file\n$curvers > $vers\n"; unlink $set1 if -f $set1; &restart("restore"); } ### my $o = ""; while(<@ARGV>){ $limit = $_ if $o eq "--limit" && $_ =~ m/^\d{1,2}$/; $o = $_; } $silent = 5 if grep/^--silent$/, @ARGV; $disabled = 0 if grep/^--enable$/, @ARGV; $disabled = 1 if (grep/^--disable$/, @ARGV) && (!grep/^--enable$/, @ARGV); my $window = &window if grep/^--settings$/, @ARGV; my ($tray, $img1, $img2) = &tray; my $main = &main; my $menu = &menu; my $submenu = &submenu; $main -> show_all; # right click menu $menu -> show_all; # items menu $submenu -> show_all; # sub-menu for items menu $tray -> show_all; # tray icon visible if (grep/^--settings$/, @ARGV){ $window -> show_all; pop @ARGV if $ARGV[@ARGV - 1] eq "--settings"; } $req = 2; # force NO update in startup &security_check; # some security checks if (grep/^--free-all$/, @ARGV){ # set all data free chmod 0644, $_ while(<$dir/data/*.clip>); } if (grep/^--lock-all$/, @ARGV){ # set all data locked chmod 0640, $_ while(<$dir/data/*.clip>); } if (grep/^--clear-all$/, @ARGV){ &clean("clipboard"); &clean("primary"); &clear("all"); } &clipboard; # starting sniffer &clear("all", "auto") if $history == 0; ## MESSAGE: Clipboard manager started $tray -> send_message($int, deco(%lang -> {prog0})) if $silent < 3 && $disabled == 0 && $tips == 1; if (-f "$dir/.disable.lock"){ $disabled = 1; &toggle_run; } else { $disabled = 0; } Gtk2 -> main; exit 0; ############################### SUBS ################################## ### CLIPBOARD (The Engine) :) sub clipboard{ my $clip = Gtk2::Clipboard -> get(Gtk2::Gdk::Atom -> intern("CLIPBOARD", 0)); $clip -> signal_connect("owner-change" => sub{&store($clip -> wait_for_text, "clipboard")}); return 0; } ### GET CURRENT (from clipboard) sub get_current{ my $target = shift; $target = "clipboard" if $target ne "primary"; my $clip = Gtk2::Clipboard -> get(Gtk2::Gdk::Atom -> intern("\U$target", 0)); return $clip -> wait_for_text, $target; } ### VIEW CURRENT (view current clipboard content) sub view_current{ my $target = shift; my ($data, undef) = &get_current($target); return $tray -> send_message($int, deco(%lang -> {msg04})) if $silent < 4 && $data eq "" && $tips == 1; unlink "$tmpfile" if -f "$tmpfile"; open FILE, "> $tmpfile" or warn "gCbm 1: $! $tmpfile"; flock FILE, 2; print FILE $data; close FILE; chmod 0444, $tmpfile; system("$editor $tmpfile &"); return 0; } ### CLEAN (clean clipboard or primary) # $clip -> clear is not working, 0115.1 sub clean{ my $target = shift; my $clip = Gtk2::Clipboard -> get(Gtk2::Gdk::Atom -> intern("\U$target", 0)); $clip -> set_text(" "); &dialog("info", "ok", "\u$target cleaned."); } ### GET NAME (get filename to data-file) sub get_name{ my $cnt = shift; my $name = time . ".clip"; $cnt++; return &get_name($cnt) if -f "$dir/data/$name" && $cnt < 3; return $name; } ### STORE (from clipboard to file) sub store{ $lock--; # 1st one after menu click need to be locked return 0 if $lock > 1 || $disabled > 0; my ($data, $target) = @_; return if $data eq ""; $req = 1; &clear("old", "auto"); foreach(<$dir/data/*.clip>){ next if !-f $_; open FILE, "< $_" or warn "gCbm error CHK1: $! $_\n"; my $tmp = join / /, ; close FILE; return print "gCbm cancel, same item exists.\n" if $data eq $tmp; } my $name = &get_name(0); open FILE, "> $dir/data/$name" or warn "gCbm 2: $! $name\n"; flock FILE, 2; print FILE $data; close FILE; if ($target eq "primary"){ $tray -> cancel_message($msgid) if $msgid; $msgid = $tray -> send_message($int, deco(%lang -> {msg06})) if $tips == 1; } $req = 1; &update_menu; return 0; } ### GET DATA (from file to clipboard) sub get_data{ my ($xfile, $target) = @_; return 0 if !-f $xfile; $target = "clipboard" if $target ne "primary"; open FILE, "< $xfile" or warn "gCbm 3: $! $xfile\n"; my @data = ; close FILE; $lock = 3; # global lock to prevent new storing of next data (3) my $clip = Gtk2::Clipboard -> get(Gtk2::Gdk::Atom -> intern("\U$target", 0)); $clip -> set_text("@data"); $req = 1; &update_menu; return 0; } ### CLEAR (remove all or > $limit data-files) sub clear{ my ($mode, $run) = @_; $req = 0; if ($run ne "auto" && $mode eq "all"){ if ($silent < 4){ return 0 if &dialog("warning", "yes-no", %lang -> {clip1}) ne "yes"; } while(<$dir/data/*.clip>){ my @stat = stat $_; unlink $_ if $stat[2] != 33184; # file locked by user $req = 1; } } if ($mode ne "all"){ my @items = <$dir/data/*.clip>; for(my $i = 0; $i < @items - $limit; $i++){ my @stat = stat $items[$i]; unlink $items[$i] if $stat[2] != 33184; $req = 1; } } &update_menu if $req == 1; if ($run ne "auto"){ my @files = <$dir/data/*.clip>; return &dialog("warning", "ok", %lang -> {clip2}) if @files > 0 && $silent < 4; } unlink $tmpfile if -f $tmpfile; return 0; } ### REMOVE (remove one data-file) sub remove{ my $xfile = shift; my $data0; my @stat = stat $xfile; return &dialog("warning", "ok", %lang -> {msg05}) if $stat[2] == 33184; if ($silent < 4){ open FILE, "< $xfile" or warn "gCbm 4: $! $xfile\n"; while(){ $data0 = $_; last if $data0 ne " "; } close FILE; $data0 = substr($data0, 0, $clip); return $req = 0 if &dialog("warning", "yes-no", %lang -> {prog2} . "\n" . $xfile . "\nCONTENT: " . encode("utf8", $data0)) ne "yes"; } unlink $xfile if -f $xfile; $req = 1; &update_menu; return &dialog("error", "ok", %lang -> {msg01} . "\n$xfile") if -f $xfile; return 0; } ### GET_PRIMARY (current primary selection) sub get_primary{ return if $primary == 0; my ($data, undef) = &get_current("primary"); return "" if !$data; $data = substr($data, 0, $clip); $data = "$data .." if length $data > $clip - 3; $data =~ s/_/\_\_/g; $data =~ s/\n/ /g; return $data; } ### UPDATE PRIMARY (primary selection menu-item) sub update_primary{ my $xtips; return if $primary == 0; my ($data, undef) = &get_current("primary"); return if $currentP eq $data; $currentP = $data; if ($tips == 1){ $xtips = Gtk2::Tooltips -> new; $xtips -> enable; $xtips -> force_window; } $menu -> remove($primM) if $primM; $primM = Gtk2::MenuItem -> new(&get_primary); $primM -> signal_connect("activate" => sub{&store($data, "primary")}); $xtips -> set_tip($primM, "Current Primary buffer\nClick to save.") if $tips == 1; $menu -> insert($primM, 4); $menu -> show_all; return; } ### UPDATE MENU (items menu) sub update_menu{ return 0 if $req == 0 || $req == 2; $menu = &menu; $menu -> show_all; $tray -> send_message($int, deco(%lang -> {msg02})) if $silent < 3 && $disabled > 0 && $tips == 1; return $req = 0; } ### CHECK PATH (is $ff in $PATH env dirs) sub check_path{ my $ff = shift; my @path = split /:/, $ENV{PATH}; if(!-x $ff){ # not a full path foreach(@path){ # search program from $PATH env paths next if !-x "$_/$ff"; return "$_/$ff" if -x "$_/$ff"; } } return; } ### OPEN WITH (open target if it's file or dir) sub open_with{ my $xfile = shift; my $menuR = Gtk2::Menu -> new; return $menuR if !-f $xfile && !-d $xfile; if (-d $xfile){ # open directory foreach(@managers){ my $ff = $_; $_ =~ s/^.*\///; $menuR -> append(my $menuR0 = Gtk2::MenuItem -> new("\u$_ ")); $menuR0 -> signal_connect("activate" => sub{system("$ff $xfile &")}); } return $menuR; } foreach(@openwith){ # open file my $ff = $_; $_ =~ s/^.*\///; $menuR -> append(my $menuR0 = Gtk2::MenuItem -> new("\u$_ ")); $menuR0 -> signal_connect("activate" => sub{system("$ff $xfile &")}); } return $menuR; } ### TOGGLE LOCK (mark file locked or free) sub toggle_lock{ my $xfile = shift; my @stat = stat $xfile; if ($stat[2] == 33184){ chmod 0644, $xfile; # free } else { chmod 0640, $xfile; # locked } $req = 1; &update_menu; return 0; } ### DECO (those umlauts and Gtk2 widgets eats me!) sub deco{ my $data = shift; return $data if $decode == 0; return decode("utf8", $data); } ### POPUP (uppop popup) sub popup{ my ($widget, $event, $params) = @_; my $ear = $event -> button(); if ($ear == 1){ # left button my ($tmpC, undef) = &get_current("clipboard"); &update_menu if $req > 0 || $tmpC ne $currentC; &update_primary; $menu -> popup(undef, undef, undef, undef, 0, 0); } $main -> popup(undef, undef, undef, undef, 0, 0) if $ear == 3; # right button if ($ear == 2){ if ($primary == 1){ # middle button &store(&get_current("primary"), "primary"); } else { $window = &window if !$window; $window -> hide_all; # Fluxbox bug 0113.2 $window -> deiconify; $window -> show_all; } } return; } ### LOCKED SUBMENU (locked items) sub locked_submenu{ my @locks = @_; my $menuR = Gtk2::Menu -> new; $menuR -> set_name("locked_menu"); # name for gtkrc my @mitms; my $i = 0; while(<@locks>){ my $xfile = $_; my ($ff, $data0) = &get_label($xfile); $menuR -> append($mitms[$i] = Gtk2::MenuItem -> new(deco($ff))); $mitms[$i] -> set_submenu(&submenu($xfile, $data0, "locked")); $i++; } return $menuR, $i; } ### GET LABEL (for menu item) sub get_label{ my $xfile = shift; my $data0; chomp $xfile; return if !-f $xfile; open FILE, "< $xfile" or die "gCbm 4: $! $xfile\n"; my $o = ""; while(){ $data0 = "$data0$_"; last if $o !~ m/^\s+$/ && $_ !~ m/^\s+$/; $o = $_; } close FILE; my $ff = $data0; $ff =~ s/[\n\r]/ /g; # Thunar case $ff = "" if $ff =~ m/^\s+$/; $ff = substr($ff, 0, $clip); $ff = "$ff .." if length $data0 > $clip - 3; $ff =~ s/^\s+//; # fixed 0118.1 $ff =~ s/_/\_\_/g; $data0 =~ s/[\n\r]//g; # Thunar case return $ff, $data0; } ### SUBMENU (items-menu submenu) sub submenu{ my ($xfile, $url, $mode) = @_; my @stat = stat "$xfile"; my $zfile; $zfile = $url if $url =~ m/^file:\/\/.*$/ || $url =~ m/^\/.*$/; $zfile =~ s/^file:\/\///; # For Thunar $zfile =~ s/[\n\r]//g; # Xfce4 Thunar and PCManFM "Copy" CR+LF $url = "" if $url !~ m/^(http|https):\/\//; my $menuR = Gtk2::Menu -> new; $menuR -> set_name("sub_menu"); # name for gtkrc if ((-f $zfile || -d $zfile) && $zfile){ $menuR -> append(my $menuR00 = Gtk2::MenuItem -> new(deco(%lang -> {mainC}))); # Open target with.. $menuR00 -> set_submenu(&open_with($zfile)); $menuR -> append(Gtk2::SeparatorMenuItem -> new); } if ($url){ $menuR -> append(my $menuR00 = Gtk2::MenuItem -> new(deco(%lang -> {subm1}))); # Open URL $menuR00 -> signal_connect("activate" => sub{system("$browser $url &")}); $menuR -> append(Gtk2::SeparatorMenuItem -> new); } $menuR -> append(my $menuR0 = Gtk2::MenuItem -> new(deco(%lang -> {subm2}))); # Select item if ($primary == 1){ $menuR -> append(my $menuR4 = Gtk2::MenuItem -> new(deco(%lang -> {subm6}))); # Primary buffer $menuR4 -> signal_connect("activate" => sub{&get_data($xfile, "primary")}); } $menuR -> append(my $menuR1 = Gtk2::MenuItem -> new(deco(%lang -> {subm3}))); # View item $menuR -> append(Gtk2::SeparatorMenuItem -> new); $menuR -> append(my $menuR2 = Gtk2::CheckMenuItem -> new(deco(%lang -> {subm4}))); # Lock item if ($mode ne "locked"){ $menuR -> append(my $menuR3 = Gtk2::MenuItem -> new(deco(%lang -> {subm5}))); # Remove item $menuR3 -> signal_connect("activate" => sub{$req = 1; &remove($xfile)}); } $menuR2 -> set_active(1) if $stat[2] == 33184; #(33184 = 0640 -> locked, 0644 = free) $menuR0 -> signal_connect("activate" => sub{&get_data($xfile, "clipboard")}); $menuR1 -> signal_connect("activate" => sub{$req = 1; system("$editor $xfile &")}); $menuR2 -> signal_connect("activate" => sub{&toggle_lock($xfile)}); return $menuR; } ### MENU (items menu, left click) sub menu{ my $menuR = Gtk2::Menu -> new; $menuR -> set_name("items_menu"); # name for gtkrc my (@mitms, $xtips); my $i = 1; if ($tips == 1){ $xtips = Gtk2::Tooltips -> new; $xtips -> enable; $xtips -> force_window; } $menuR -> append(my $xitem = Gtk2::ImageMenuItem -> new(" gClip : Clipboard Manager")); $xitem -> set_image(Gtk2::Image -> new_from_stock("gtk-paste", "menu")); $xitem -> signal_connect("activate" => sub{$main -> popup(undef, undef, undef, undef, 0, 0)}); $menuR -> append(Gtk2::SeparatorMenuItem -> new); ($currentC, undef) = &get_current("clipboard"); $currentC =~ s/_/\_\_/g; $currentC =~ s/[\r\n]/ /g; $currentC = "" if !$currentC; $currentC = substr($currentC, 0, $clip); $currentC = "$currentC .." if length $currentC > $clip - 3; $currentC =~ s/^\s+/ /g; $menuR -> append(my $clipM = Gtk2::MenuItem -> new($currentC)); $clipM -> signal_connect("activate" => sub{&view_current}); $menuR -> append(Gtk2::SeparatorMenuItem -> new); $xtips -> set_tip($clipM, "Current Clipboard content\nClick to view.") if $tips == 1; if ($primary == 1){ $menuR -> append($primM = Gtk2::MenuItem -> new(&get_primary)); $primM -> signal_connect("activate" => sub{&store(&get_current("primary"), "primary")}); $menuR -> append(Gtk2::SeparatorMenuItem -> new); $xtips -> set_tip($primM, "Current Primary buffer\nClick to save.") if $tips == 1; } my @items; my @locks; unshift(@items, $_) while(<$dir/data/*.clip>); foreach(@items){ my $xfile = $_; next if !-f $xfile; my ($ff, $data0) = &get_label($xfile); my @stat = stat $xfile; if ($stat[2] != 33184 && $i <= $limit){ # only free files allowed *2 $menuR -> append($mitms[$i] = Gtk2::MenuItem -> new("$i. $ff ")); $mitms[$i] -> set_submenu(&submenu($xfile, $data0, "free")); $i++; } elsif ($stat[2] == 33184) { push(@locks, $xfile); } } $menuR -> append(my $mi1 = Gtk2::MenuItem -> new(deco(%lang -> {prog5}))) if $i < 2; # No items $menuR -> append(Gtk2::SeparatorMenuItem -> new); my ($locked_menu, $cnt) = &locked_submenu(@locks) if @locks > 0; $cnt = 0 if !$cnt; $menuR -> append(my $mi2 = Gtk2::MenuItem -> new(deco(%lang -> {prog6} . " ($cnt)"))); $mi2 -> set_submenu($locked_menu) if $cnt > 0; $menuR -> append(Gtk2::SeparatorMenuItem -> new); $menuR -> append(my $mi3 = Gtk2::ImageMenuItem -> new_from_stock("gtk-clear")); $mi3 -> signal_connect("activate" => sub{&clear("all")}); return $menuR; } ### MAIN (main menu, right click) sub main{ my $space = " "; # getting some width to the menu ;) my $main = Gtk2::Menu -> new; $main -> set_name("main_menu"); # name for gtkrc $main -> append($mainA = Gtk2::ImageMenuItem -> new_from_stock("gtk-disconnect")); $main -> append(Gtk2::SeparatorMenuItem -> new); $main -> append(my $main4 = Gtk2::ImageMenuItem -> new_from_stock("gtk-clear")); $main -> append(Gtk2::SeparatorMenuItem -> new); $main -> append(my $main1 = Gtk2::ImageMenuItem -> new_from_stock("gtk-preferences")); $main -> append(my $mainC = Gtk2::ImageMenuItem -> new_from_stock("gtk-help")); $main -> append(my $mainB = Gtk2::ImageMenuItem -> new("About gClip$space")); $mainB -> set_image(Gtk2::Image -> new_from_stock("gtk-about", "menu")); $main -> append(Gtk2::SeparatorMenuItem -> new); $main -> append(my $main5 = Gtk2::ImageMenuItem -> new_from_stock("gtk-quit")); $mainA -> signal_connect("activate" => sub{$enable = 1; &toggle_run}); $mainB -> signal_connect("activate" => sub{&dialog("info", "ok", &about)}); $mainC -> signal_connect("activate" => sub{&dialog("info", "ok", &help)}); $main1 -> signal_connect("activate" => sub{$window = &window if !$window; $window -> hide_all; # for Fluxbox bug $window -> deiconify; $window -> show_all;}); $main4 -> signal_connect("activate" => sub{&clear("all")}); $main5 -> signal_connect("activate" => sub{&quit}); return $main; } ### SHOW HINT sub show_hint{ return if $silent > 3 || $tips == 0; my ($hint, $int) = @_; my $cnt = my @tmp = <$dir/data/*.clip>; my $run = "Running" if $disabled == 0; $run = "Stopped" if $disabled > 0; $hint = "gClip Clipboard Manager\n$run, $cnt items." if !$hint; $msgid = $tray -> send_message($int, deco($hint)); return; } ### TRAY (tray and stop/run images) sub tray{ my $tray; my $tray = Gtk2::TrayIcon -> new("\u$name"); $ebox = Gtk2::EventBox -> new; # tray-icon click-box $ebox -> signal_connect("button-release-event" => \&popup); $ebox -> signal_connect("enter-notify-event" => sub{&show_hint(undef, 5)}); $ebox -> signal_connect("leave-notify-event" => sub{$tray -> cancel_message($msgid) if $msgid}); my $img1 = Gtk2::Image -> new_from_stock("gtk-paste", "menu"); # tray image, normal my $img2 = Gtk2::Image -> new_from_stock("gtk-stop", "menu"); # tray image, stopped $ebox -> add($img1); $tray -> add($ebox); return $tray, $img1, $img2; } ### WINDOW sub window{ my $window = Gtk2::Window -> new("toplevel"); $window -> set_name("main_window"); # name for gtkrc $window -> set_title("gClip " . deco(%lang -> {main1})); $window -> set_position("center"); $window -> set_default_icon_name("gtk-paste"); $window -> set_border_width(10); $window -> stick; $window -> set_resizable(0); $window -> set_keep_above(1); $window -> set_skip_taskbar_hint(1); $window -> set_skip_pager_hint(1); $window -> set_auto_startup_notification(0); $window -> signal_connect(delete_event => sub{$window -> hide_all; # Fluxbox fix return $window}); $window -> signal_connect(destroy => \&quit); # not in use anymore, but keep just in case my $vbox1 = Gtk2::VBox -> new(0, 10); my $hbox2 = Gtk2::HBox -> new(0, 0); my $hbox3 = Gtk2::HBox -> new(0, 0); my $hbox4 = Gtk2::HBox -> new(0, 0); my $hbox5 = Gtk2::HBox -> new(0, 0); my $hbox6 = Gtk2::HBox -> new(0, 0); my $lbox1 = Gtk2::HBox -> new(0, 0); # for Gtk2 justify-bug 0118 my $lbox2 = Gtk2::HBox -> new(0, 0); my $lbox3 = Gtk2::HBox -> new(0, 0); my $lbox4 = Gtk2::HBox -> new(0, 0); my $imgx = Gtk2::Image -> new_from_stock("gtk-paste", "large-toolbar"); my $labA = &pango_label("" . %lang -> {main9} . ""); # Menu items my $labB = &pango_label("" . %lang -> {mainA} . ""); # Programs my $labC = &pango_label("" . %lang -> {mainB} . ""); # General my $labD = &pango_label("" . %lang -> {mainC} . ""); # Open with.. my $ent1 = Gtk2::SpinButton -> new_with_range(2, 99, 1); my $lab1 = Gtk2::Label -> new(deco(%lang -> {main2})); # Items visible my $ent2 = Gtk2::SpinButton -> new_with_range(10, 99, 1); my $lab2 = Gtk2::Label -> new(deco(%lang -> {main3})); # Item length my $ent3 = &entry(90); my $lab3 = Gtk2::Label -> new(deco(%lang -> {main5})); # Browser my $ent4 = &entry(90); my $lab4 = Gtk2::Label -> new(deco(%lang -> {main6})); # Editor my $ent5 = &entry(350); # Open with.. my $sel0 = Gtk2::CheckButton -> new(deco(%lang -> {mainE})); # Primary buffer $sel0 -> set_active($primary); $sel0 -> signal_connect("clicked" => sub{$primary = !$primary}); my $sel1 = Gtk2::CheckButton -> new(deco(%lang -> {main7})); # History $sel1 -> set_active($history); $sel1 -> signal_connect("clicked" => sub{$history = !$history}); my $sel2 = Gtk2::CheckButton -> new(deco(%lang -> {main8})); # Autostart $sel2 -> set_active($autostart); $sel2 -> signal_connect("clicked" => sub{$autostart = !$autostart}); my $sel3 = Gtk2::CheckButton -> new(deco(%lang -> {mainF})); # Show tooltips $sel3 -> set_active($tips); $sel3 -> signal_connect("clicked" => sub{$tips = !$tips}); $ent1 -> set_value($limit); $ent2 -> set_value($clip); $ent3 -> set_text($browser); $ent4 -> set_text($editor); $ent5 -> set_text(join " ", @openwith); my $but1 = Gtk2::Button -> new_from_stock("gtk-save"); my $but2 = Gtk2::Button -> new_from_stock("gtk-close"); $but1 -> set_size_request(80, 32); $but2 -> set_size_request(80, 32); my $but3 = Gtk2::Button -> new(deco(%lang -> {mainD})); $but2 -> signal_connect("clicked" => sub{$window -> hide_all}); $but3 -> signal_connect("clicked" => sub{unlink $set1; &restart("restore")}); $but1 -> signal_connect("clicked" => sub{&save_settings($ent1, $ent2, $ent3, $ent4, $ent5, $sel1, $sel2, $sel0, $sel3); &restart;}); $vbox1 -> pack_start($imgx, 0, 0, 0); $lbox1 -> pack_start($labA, 0, 0, 5); $vbox1 -> pack_start($lbox1, 0, 0, 0); $hbox2 -> pack_end($ent2, 0, 0, 5); $hbox2 -> pack_end($lab2, 0, 0, 0); $hbox2 -> pack_end($ent1, 0, 0, 5); $hbox2 -> pack_end($lab1, 0, 0, 0); $vbox1 -> pack_start($hbox2, 0, 0, 0); $lbox2 -> pack_start($labB, 0, 0, 5); $vbox1 -> pack_start($lbox2, 0, 0, 0); $hbox3 -> pack_end($ent4, 0, 0, 5); $hbox3 -> pack_end($lab4, 0, 0, 0); $hbox3 -> pack_end($ent3, 0, 0, 5); $hbox3 -> pack_end($lab3, 0, 0, 0); $vbox1 -> pack_start($hbox3, 0, 0, 0); $lbox3 -> pack_start($labD, 0, 0, 5); $vbox1 -> pack_start($lbox3, 0, 0, 0); $vbox1 -> pack_start($ent5, 0, 0, 0); $lbox4 -> pack_start($labC, 0, 0, 5); $vbox1 -> pack_start($lbox4, 0, 0, 0); $hbox4 -> pack_start($sel2, 0, 0, 5); $hbox4 -> pack_start($sel1, 0, 0, 5); $vbox1 -> pack_start($hbox4, 0, 0, 0); $hbox5 -> pack_start($sel0, 0, 0, 5); $hbox5 -> pack_start($sel3, 0, 0, 5); $vbox1 -> pack_start($hbox5, 0, 0, 0); $hbox6 -> pack_end($but1, 0, 0, 5); $hbox6 -> pack_end($but2, 0, 0, 5); $hbox6 -> pack_end($but3, 0, 0, 5); $vbox1 -> pack_start($hbox6, 0, 0, 8); $window -> set_focus($but2); $window -> add($vbox1); return $window; } ### PANGO LABEL # fixed 0118 sub pango_label{ my $markup = shift; my $label = Gtk2::Label -> new(deco("$markup")); $label -> set_use_markup(1); return $label; } ### ENTRY sub entry{ my $width = shift; my $entry = Gtk2::Entry -> new; $entry -> set_size_request($width, 26); return $entry; } ### DIALOG (message-dialog) sub dialog{ my ($mode, $button, $msg) = @_; print "gCbm - $msg\n" if $silent < 2; my $dialog = Gtk2::MessageDialog -> new_with_markup(undef, [qw/modal destroy-with-parent/], $mode, $button, deco("$msg")); $dialog -> set_name("dialog"); # name for gtkrc $dialog -> set_position("center"); $dialog -> set_title($name); my $resp = $dialog -> run; $dialog -> destroy; return $resp; } ### ABOUT (DO NOT TOUCH THIS, thank You!) ;) sub about{ return <gClip\n Clipboard Manager, $vers\n Proudly handmade with Perl, raimo 2007-2009 http://gclip.uhvo.org\n Program directory: $dir\n License: $license ABOUT # } ### HELP sub help{ return < get_value if $ent1 -> get_value =~ m/^\d{1,2}$/; # 2-99 items $clip = $ent2 -> get_value if $ent2 -> get_value =~ m/^\d{2}$/; # 10-99 marks $browser = $ent3 -> get_text if length $ent3 -> get_text > 0; $editor = $ent4 -> get_text if length $ent4 -> get_text > 0; @openwith = $ent5 -> get_text if length $ent5 -> get_text > 0; $primary = $sel0 -> get_active; $primary = 0 if !$primary; $history = $sel1 -> get_active; $history = 0 if !$history; $autostart = $sel2 -> get_active; $autostart = 0 if !$autostart; $tips = $sel3 -> get_active; $tips = 0 if !$tips; # Creating new settings file unlink $set1 if -f $set1; &install; &update_menu; return 0; } ### GET OPENWITH (are @openwith programs in $PATH env dirs) sub get_openwith{ my @tmp; foreach(@openwith){ my $ff = &check_path($_); next if !$ff; push(@tmp, $_); } return @tmp; # new @openwith } ### GET MANAGERS (file manager wizard) sub get_managers{ my @find = ("thunar", "pcmanfm", "rox-filer", "nautilus"); my @managers; foreach(@find){ my $manager = &check_path($_); next if !$manager; push(@managers, $manager); } return @managers; } ### GET EDITOR (we need editor!) :) sub get_editor{ my $editor; my @find = ("gtktview", "gpad", "leafpad", "mousepad", "scite", "teaed", "cream", "gedit", "kate", "kedit"); return $ENV{"VISUAL"} if -x $ENV{"VISUAL"}; while(<@find>){ next if !-x "/usr/bin/$_" && !-x "$_"; $editor = $_; last if $editor; } $editor = "xterm -e nano" if !$editor; return $editor; } ### SET SETTING IN FILE: $HOME/.gclip/gcliprc ### Reset settings by removing setting file and restarting gClip ### INSTALL [DO NOT EDIT SETTINGS HERE, USE SETTINGS FILE] sub install{ mkdir $dir if !-d $dir; mkdir "$dir/data" if !-d "$dir/data"; die &dialog("error", "ok", "Error, $! $dir/data\n") if !-d "$dir/data"; $disabled = 0 if $disabled > 2; my $settings = < 4 full silent \$limit = $limit; # items in menu (data-files alive), default 20 \$clip = $clip; # menu-item length marks, default 30 \$primary = $primary; # 0/1 use primary buffer \$browser = "$browser"; # Your favourite www-browser \$editor = "$editor"; # Your favourite text-file editor \$decode = $decode; # 0/1 decode for umlauts, set 0 if english \@openwith = qw(@openwith); #\n# Add open with programs as: \@openwith = qw(prog1 prog2 prog3);\n # Count of data files over \$limit will be removed automatically\n \$set_test = "OK"; # do not change this\n SETTINGS # if (!-f "$set1"){ open FILE, "> $set1"; flock FILE, 2; print FILE "$settings\n\n"; close FILE; } ## AUTOSTART (works with Xfce4 and Gnome, not sure for others) my $xdir = "$home/.config"; my $xfile = "$xdir/autostart/gclip.desktop"; if ($autostart == 1 && !-f $xfile){ my $desk = < $xfile"; flock FILE, 2; print FILE "$desk\n"; close FILE; chmod 0700, $xfile; } return 0; } ### SECURITY CHECK (not all allowed) sub security_check{ $tray -> send_message(10, deco(%lang -> {warn1})) if $> == 0; # root-user warning die &dialog("warning", "ok", %lang -> {warn2} . "\nread: nano $0") # wrong installation error if ($0 !~ m/^\/usr\/bin\//) || !-x $0 || ($dir ne "/home/$user/.gclip"); die &dialog("warning", "ok", %lang -> {warn3} . $dir) if !-w $dir; # can't write error my $xfile = "$home/.config/autostart/gclip.desktop"; unlink $xfile if -f $xfile && $autostart == 0; return 0; } ### TOGGLE RUN (stop/run) sub toggle_run{ if ($disabled == 2 || $disabled == 3){ print "gCip -> online\n"; #0119 $disabled = 0 if $enable == 1; # mark for quit unlink "$dir/.disable.lock" if $enable == 1 && -f "$dir/.disable.lock"; return &restart; } $main -> remove($mainA); $main -> insert($mainA = Gtk2::ImageMenuItem -> new_from_stock("gtk-connect"), 0); $mainA -> signal_connect("activate" => sub{$enable = 1; &toggle_run}); $main -> show_all; $ebox -> remove($img1); $ebox -> add($img2); print "gClip -> offline\n"; #0119 $ebox -> show_all; ## MESSAGE: Clipboard manager stopped &dialog("info", "ok", %lang -> {prog3}) if $silent < 1; $tray -> send_message($int + 2, deco(%lang -> {prog3})) if $silent < 3 && $tips > 0; if (!-f "$dir/.disable.lock"){ open FILE, "> $dir/.disable.lock" or warn "$! $dir/.disable.lock\n"; flock FILE, 2; print FILE "$name disabled lock-file\n"; close FILE; chmod 0444, "$dir/.disable.lock"; } return $disabled = 2; } ### RESTART sub restart{ my $mode = shift; push(@ARGV, "--enable") if $disabled == 2; push(@ARGV, "--settings") if $mode eq "restore"; # fly fixed ;) 0113.2 system("$0 @ARGV &"); &quit("restart"); return 0; } ### QUIT sub quit{ my $mode = shift; &clear("old", "auto"); unlink "$dir/.disable.lock" if $disabled == 0 && -f "$dir/.disable.lock"; if ($history == 0 && $mode ne "restart"){ print %lang -> {msg03}; &security_check; my @files = <$dir/data/*.clip>; unlink $_ while(<@files>); print "OK\n" if @files == 0; &dialog("warning", "ok", %lang -> {warn4}) if @files > 0; } $tray -> destroy if $tray; Gtk2 -> main_quit; sleep(1); #0119 print "gClip stopped press CTRL+C if stopped (if restarting... do NOT press anything.)\n"; #0119 exit 0; } __END__ URL: http://gclip.uhvo.org SOURCE: http://gclip.uhvo.org/sec/gclip.txt HELP: http://gtk2-perl.sourceforge.net/doc/gtk2-perl-tut/ http://www.gtk.org/tutorial/ http://gclip.uhvo.org gclip.mail@gmail.com TODO: Is there that tray-area at all - checking? But HOW to do it? ARGH!? Images would be nice? (fly) :) .. but no images at this time, sorry. Very-Hot-Keys! FIXED: almost everything is double-fixed already ;D Fixed mystical autostart bug in prefs and on/off-lined 0119 new version 20090803 for that bugfix and many other reasons. Changed license to CC BY-NC-SA 0118.1 1st stable 0118 Some fixes 0117.3 Tiny tuning + small fixes 0117.2 Directory opening + File Manager finder added 0117.1 Nautilus, Thunar, PCManFM and Rox-Filer are suported Fixes for Open with programs checking, faster now Huge set of small fixes 0116.1 New Show tooltips setting 0116.1 Open target uses $PATH env now 0116.1 Fixed lang.lng file 0116.1 Tooltips for Clipboard and Primary, $silent > 3 hides 0115.2 Lightspeed edition, file-read fixed. 0115.1 Xfce4 Thunar "Copy" CR+LF case fixed. Window back to dynamic and "beta-time" settings autoreset system for update. Primary selection! Yes, THE Primary Selection! :) 0114.0 .. 0114.3 Set "Use primary selection" to 1 and save PS with tray-icon middle or menu 3rd line left -click Locked files to own submenu 0114.3 Locked files and $limit are not in sync now -> Very OK 0114.3 Fixed empty data + default-settings + window closing 0113.2 Fix for Fluxbox bugs + small tuning 0113.1 Public! 0112.1 Very near stable BETA now! ;) 0111.7 HELP: http://gclip.uhvo.org gclip.mail@gmail.com license: http://creativecommons.org/licenses/by-nc-sa/1.0/ Author: raimo http://gclip.uhvo.org # file_end: /usr/bin/gclip