#!/usr/bin/perl
# (c) Denis Kaganovich, Anarchy or GPLv2
# openbox config pipe menu v0.12
# using /usr/share/openbox*/rc.xsd
%xml_=('<'=>'lt','>'=>'gt',"'"=>'apos','"'=>'quot','&'=>'amp');
$xml_=join('',keys %xml_);
$MENU='config';
$bin='/usr/bin/';
$allfont='MenuItem';
$xrfont='!Font';
$cursor='Xcursor.theme';
%gtk=(
$xrfont=>'gtk-font-name',
'Xcursor.theme'=>'gtk-cursor-theme-name',
'Xcursor.size'=>'gtk-cursor-theme-size',
);
my %config=(
'config_mode'=>{
($config_mode='TREE/xml')=>sub{($sel1,$sel2,$xml_mode)=('*')},
'tree/XML'=>sub{($sel1,$sel2,$xml_mode)=('<','>',1)},
},
);
$SIG{__DIE__}=sub{
return if(!exists($ENV{DBUS_SESSION_BUS_ADDRESS}));
print STDERR ($e=join('',"$0: ",@_));
exec($bin.'notify-send','-t',6000,$e);
};
sub utf8_encode{};
sub utf8_decode{};
if(1){ # perl version?
*utf8_encode=*utf8::encode;
*utf8_decode=*utf8::decode;
# $filemode=':utf8';
}
for(@ARGV){
if($_=~s/^--//){
undef $P{$i=$_};
}else{
push(@{$P{$i}},$_);
}
}
if(exists($P{''}) && substr(scalar(%P),0,2) eq '1/'){
# $P{profile}='ya';
$P{fix}=$P{''};
}
if($prof=$P{profile}->[0]){
$cfg="$ENV{HOME}/.config/$prof";
@rc=("$cfg/rc.xml");
}else{
if(!($cfg=$P{config}->[0])){
my $s=`${bin}obxprop --root`;
$s=~s/^([A-Z0-9_]*)\([A-Z0-9_]*\) = (\"[^\"]*\"(?:, \"[^\"]*\")*|[^\n]*)$/$prop{$1}=$2;''/gme;
$cfg=$prop{'_OB_CONFIG_FILE'};
$cfg=~s/\"//gs;
}
@rc=($cfg);
($cfg=~s/[^\/]*$//) &&
(($prof)=$cfg=~/.*\/([^\/]+)\/*$/);
}
push @rc,"/etc/xdg/$prof/rc.xml";
my (%type,%xsd,%fontcfg,%fonts,%fonts_,%CNT,%xtype,%add_del,%add);
$fix=$0.' --fix';
$fix=~s/^\/usr\/bin\///gs if(!$bin);
$ID='ob:openbox_config';
%call_tags=(
'font'=>0,
# 'ob:action'=>0,
# 'ob:actionname'=>0,
# 'action'=>0,
);
%subinit=(
'ob:openbox_config:theme:font'=>\&fonts,
);
%xtype=(
'ob:keyname'=>"
",
'ob:button'=>"",
);
%menu=(
'ob:keyname'=>\&keymap,
'ob:button'=>\&button,
'setxkbmap'=>\&setxkbmap,
'.Xresources'=>\&xresources,
);
%cmd=(
'fix'=>\&fixrc,
'id'=>sub{
$ID=$_[0];
&{$subinit{$ID}}(@_) if(exists($subinit{$ID}));
$MENU=join(':',$MENU,@_);
},
# current submenu design looks not best, but simple, fast & recursive
'menu'=>sub{
my $m=shift;
for(@_){
~s/([$xml_])/\&$xml_{$1};/gs;
$_="'$_'"
}
$MENU.=":$m:$_[1]:$$";
print '';
&{$menu{$m}}(@_);
print '';
exit;
},
);
%stdio=('<-'=>*STDIN,'>-'=>*STDOUT);
sub load_xml{
my $F=$_[0];
my $s;
if(my $l=-s $F){
flock($F,1) if($_[1]==2);
read($F,$s,$l) || die $!;
close($F);
}else{
while(<$F>){$s.=$_}
}
utf8_decode($s);
### bugfix
if($_[1]==1){
$s=~s/name=\"monitor\" type=\"ob:primarymonitor\"/name=\"primaryMonitor\" type=\"ob:primarymonitor\"/;
$s=~s/:enumeration value=\"\[0-/:pattern value=\"[0-/;
if(!($s=~/\"(?:Active|Inactive)OnScreenDisplay\"/)){
$s=~s/()/$1Active$2$1Inactive$2/;
}
}elsif($_[1]==2){
_fixrc($s);
}
### /bugfix
$s=~s//
$1 && exists($config{$1}) && exists($config{$1}->{$2}) && &{$config{$1}->{${$1}=$2}}();
''/gse;
$s=~s/<\?.*?\?>//gs;
my @tag=({});
$s=~s/<([\/!]?)([^<>\s\/]*)([^<>]*?)(\/?)>([^<]*)/
my ($c,$i);
if($1 eq '\/'){
pop @tag;
$c=$tag[-1];
}else{
push @tag,$c=\%{$tag[-1]->{$xml_mode?scalar(keys %{$tag[-1]}):0}->{$2}->{$3}};
if($4 || $1){
$c=pop @tag
}elsif(exists($c->{1})){
$i=1;
}
}
if(my ($x)=$5=~\/^\s*(\S.*?)\s*$\/s){
if($xml_mode){
$i+=scalar(keys %$c)||1;
for(my $ii=$i; ref($c->{$i}); $i--||($i=$ii+1)){};
}else{
$i+=(scalar(keys %$c)-exists($c->{0}))||1;
}
$c->{$i}.=$c->{$i} ne ''?' '.$x:$x;
}
''/ges;
map{ref($_)?$_:()}(values %{$tag[0]});
}
%xsd_class=(
'xsd:attribute'=>'=',
);
sub sort_xsd{
for my $t(keys %{$_[0]}){
for my $a(keys %{$_[0]->{$t}}){
my $id=$_[1];
my $i=$a;
my %a;
$i=~s/([^=\s]+)(?:=\"([^=\"]*)\"|=\'([^=\']*)\'|)/$a{$1}=$2;''/gse;
$id.=':'.($a{'name'}.=$xsd_class{$t}) if(exists($a{'name'}));
$type{$id}=$a{'type'} if(exists($a{'type'}));
while(my ($x,$y)=each %a){
push @{$xsd{$t}->{$id}->{$x}},$y;
}
ref($_) && sort_xsd($_,$id) for(values %{$_[0]->{$t}->{$a}});
}
}
}
sub _call{
for(@{$_[1]},$_[0]){
# next if(!exists($call_tags{$_}) && ref($_) ne SCALAR_);
next if(!exists($call_tags{$_}));
my $i=$_[0];
$i=~s/([$xml_])/\&$xml_{$1};/gs;
$_[2]=~s/\'>$/:\' execute='$0 --id $i $CNT{$_[0]}'\/>/s;
utf8_encode($_[2]);
return print $_[2];
}
}
sub cmpid{
if(($_[1]=$_[0] cmp $ID)<0){
$_[2]=1;
return index($ID.':',$_[0].':');
}elsif($_[1]>0){
return index($_[0].':',$ID.':');
}
exists($P{id}) && $P{id}->[1] ne $CNT{$_[0]}
}
sub _add_del{
return; # under construction
my ($id,$type,$ed)=(@_);
my $i;
if(exists($add{$id})){
my $ed1=$ed;
$ed1=~s/^(.*)\<(.*?)$/$1<\/$2/;
$i.="- $fix $stub{$type} $ed1
";
}
if(exists($add_del{$id})){
my $x=$id;
$x=~s/^.*://;
$i.="- $fix $type $ed -
";
}
$i.='' if(defined($i));
$i;
}
sub _chk_attr{
my $i=$_[1];
my $a=$_[2];
($i=~s/( $a[\"\'])[^\'\"]*([\'\"])/$1$_[3]$2/) && exists($_[0]->{$i});
}
sub sort_tags{
for my $t(sort keys %{$_[0]}){
my $id="$_[1]:$t";
$CNT{$id}++;
cmpid($id,my $cmpid,my $silent) && next;
my $submenu;
my $A=scalar(keys %{$_[0]->{$t}});
my $type=$type{$id};
my $menu1;
if($cmpid){
my $i=$t;
$i=~s/_/-/g;
$i.=(keys %{$_[0]->{$t}})[0] if($A==1);
$i=~s/([$xml_])/\&$xml_{$1};/gs;
$conf++;
$submenu="';
}else{
$conf++;
print "' if($lx ne '');
print "';
}
print '' if($lx ne '');
}
sub _conffile{
my ($f,$r,$r1,$v,$x,$a)=@_;
my $s;
if(open(my $F,'<',$f)){
read($F,$s,-s $F);
close($F);
}elsif(-e $f){
die $!;
}
my $i=quotemeta($v);
my $ss='[ ]*';
if(!($s=~s/^($ss$i$ss$r$ss).*?$/$1$x/gm)){
defined($a)||return;
my $s1=($a&&"$a\n")."$v$r1$x";
my $a1=quotemeta($a);
if(!($a && ($s=~s/^$ss$a1$ss$/$s1/m))){
$s.="\n" if($s ne '' && substr($s,-1) ne "\n");
$s.="$s1\n";
}
}
_write($f,$s);
$s;
}
sub getcfg{
my @font;
for(load_xml(open_($F,'<',@rc),2)){
for my $f (walk($_,undef,undef,undef,'theme',undef,undef,'font',['\splace=["\']'.$allfont.'["\']'],undef)){
for('name','weight','slant','size'){
for(walk($f,$_,undef,undef)){
push @font,$_ if(!ref($_) && $_ ne 'normal');
}
}
}
}
("@font");
}
sub _gtk1{
return $_[0]=$gtk{$_[0]} if(exists($gtk{$_[0]}));
$_[0]=~s/\./-/g;
$_[0]='gtk-'.lc($_[0]);
}
sub _xrdb{
my $i="$_[0]: $_[1]";
for('all','screen','screens','global'){
# for('all','screen'){
open_($F,'|-',$bin."xrdb -merge -$_");
print $F "$i\n";
close($F);
$? && die "Merging xrdb: '$i'";
}
_conffile("$ENV{HOME}/.Xresources",':',': ',@_,'')
}
sub xresources{
my @xfiles=(
['.gtkrc-2.0','=','=',\&_gtk1,
sub{$_[0]="\"$_[0]\"" if($_[0]=~/\D/)},
sub{eval q(
#dev-perl/gtk2-perl
use Gtk2 '-init';
my $event = Gtk2::Gdk::Event->new("GDK_CLIENT_EVENT");
$event->send_event(1);
$event->window(undef);
$event->message_type(Gtk2::Gdk::Atom->intern("_GTK_READ_RCFILES", 0));
$event->data_format(8);
$event->data('burp');
Gtk2::Gdk::Event->send_clientmessage_toall($event);
);},''],
['.config/gtk-3.0/settings.ini','=','=',\&_gtk1,
sub{$_[0]="$_[0]" if($_[0]=~/\D/)},
sub{},'[Settings]'],
['.xscreensaver',':',': ',sub{$_[0]=~s/^xscreensaver\.//},sub{},sub{}],
['.config/Trolltech.conf','=','=',sub{
return $_[0]='font' if($_[0] eq $xrfont);
},sub{
$_[0]='"'.join(',',$fontn,$fontsz,-1,5,$fontb*25+50,$fonti+0,'0,0,0,0').'"';
},sub{},'[Qt]'],
);
my $b=[0,1];
my $bb=['True','False'];
my $tm=[1,3,5,10,15,30,40,60,90,120];
($font)=getcfg();
$fontn=$font;
$fontb=($fontn=~s/ bold / /);
$fonti=($fontn=~s/ italic / /);
($fontn,$fontsz)=$fontn=~/^(.*?)\s+(\d+)$/;
my %xrparam=(
'Xft.antialias'=>$b,
'Xft.autohint'=>$b,
'Xft.hinting'=>$b,
'Xft.hintstyle'=>['hintnone','hintslight','hintmedium','hintfull'],
'Xft.rgba'=>['rgb','bgr','vrgb','vbgr','none'],
'Xft.lcdfilter'=>['lcdnone','lcddefault','lcdlight','lcdlegacy'],
'Xft.embolden'=>$b,
'Xft.minspace'=>$b,
'Xft.render (Xft client-side)'=>$b,
'Xft.core (Xft server-side)'=>$b,
'Xft.embeddedbitmap'=>$b,
'Xft.verticallayout'=>$b,
'Xcursor.theme'=>['default',(map{substr($_,28)}glob('/usr/share/cursors/xorg-x11/*'))],
'Xcursor.theme_core'=>$b,
'Xcursor.size'=>[3..100],
'xscreensaver.timeout'=>$tm,
'xscreensaver.cycle'=>$tm,
'xscreensaver.dpmsEnabled'=>$bb,
'xscreensaver.dpmsStandby'=>$tm,
'xscreensaver.dpmsSuspend'=>$tm,
'xscreensaver.dpmsOff'=>$tm,
'xscreensaver.dpmsQuickOff'=>$bb,
'xscreensaver.mode'=>['random','random-same','one','blank','off','X','lock'],
'xscreensaver.passwdTimeout'=>$tm,
'xscreensaver.lock'=>$bb,
'xscreensaver.lockTimeout'=>$tm,
'xscreensaver.visualID'=>['default','best','mono','gray','color','GL'], # number: xdpyinfo
'xscreensaver.installColormap'=>$bb,
'xscreensaver.splash'=>$bb,
'xscreensaver.nice'=>[-20..19],
'xscreensaver.fade'=>$bb,
'xscreensaver.unfade'=>$bb,
'xscreensaver.fadeSeconds'=>[0..20],
'xscreensaver.fadeTicks'=>[5,10,20,30,40],
'xscreensaver.ignoreUninstalledPrograms'=>$bb,
'xscreensaver.GetViewPortIsFullOfLies'=>$bb,
#'xscreensaver.selected'=>,
'xscreensaver.procInterrupts'=>$bb,
#'XTerm.*.faceName'=>'*',
#'XTerm.*.faceSize'=>$fontsz,
'XTerm.*.faceName'=>['','*','Monospace','Monospace:style=bold','Monospace:antialias=false','Monospace:antialias=false:style=bold',sort keys %{fc_list([1],['True'],[])}],
'XTerm.*.faceNameDoublesize'=>['','*','Monospace:style=Italic','Monospace:style=bold',sort keys %{fc_list([1],['True'],[])}],
'XTerm.*.faceSize'=>['',5..32],
'XTerm.*.reverseVideo'=>$bb,
'XTerm.*.scrollBar'=>$bb,
'XTerm.*.rightScrollBar'=>$bb,
'XTerm.*.selectToClipboard'=>$bb,
'XTerm.*.jumpScroll'=>$bb,
'XTerm.*.fastScroll'=>$bb,
'XTerm.*.multiScroll'=>$bb,
'XTerm.*.saveLines'=>[100,500,1000,5000],
'XTerm.*.autoWrap'=>$bb,
'XTerm.*.titeInhibit'=>$bb,
'XTerm.*.visualBell'=>$bb,
'XTerm.*.scrollKey'=>$bb,
'XTerm.*.scrollTtyOutput'=>$bb,
'XTerm.*.utf8Title'=>$bb,
'XTerm.keyboardType'=>['unknown','default','legacy','hp','sco','sun','tcap','vt220'],
'XTerm.scaleHeight'=>[0.9,1.0,1.1,1.2,1.3,1.4,1.5],
"$xrfont (=$allfont)"=>$font,
'font'=>['','auto','[xfontsel]'],
#'Xft.dpi'=>
#'Xft.scale'=>
#'Xft.maxglyphmemory'=>
);
my %exec=(
'[xfontsel]'=>sub{`$bin/xfontsel -print -fn ''`},
);
my %cname=(
'font'=>['*font'],
);
my %def=(
'font'=>'auto',
);
my %auto=(
'font: auto'=>sub{"-misc-fixed-medium-r-normal-*-$fontsz-*-*-*-*-*-*-*"},
);
#my $s=`${bin}xrdb -query`;
#$? && return;
my $s=$prop{'RESOURCE_MANAGER'};
$s=~s/^\"//s;
$s=~s/\"$//s;
$s.="\n";
my $ss='[ ]*';
my %x;
for(keys %xrparam){
my $y=$xrparam{$_};
my $m=$_;
$m=~s/ .*//g;
if(ref($y)){
my $x=quotemeta($m);
($x{$m})=$s=~/^$x:$ss(.*?)$ss$/m or ($x{$m}=$def{$m});
}elsif($y ne $x{$m}){
_xrdb($m,$x{$m}=$y);
}
my $x="$m: $x{$m}";
$x=exists($auto{$x})?&{$auto{$x}}():$x{$m};
for(@{$cname{$m}}){
_xrdb($_,$x{$_}=$xrparam{"$_ (=$m)"}=$x) if($x{$_} ne $x);
}
}
for(@xfiles){
my $f;
my $fn="$ENV{HOME}/$_->[0]";
if(open(my $F,'<',$fn)){
read($F,$f,-s $F);
close($F);
}elsif(defined($_->[6])){
my $d=$fn;
mkdir($d) if($d=~s/\/[^\/]*$//);
}else{
next;
}
for my $x (keys %xrparam){
my $y=$xrparam{$x};
my $m=$x;
$m=~s/ .*//g;
my $i=$m;
my $r=$_->[1];
&{$_->[3]}($i) || next;
my $i1=quotemeta($i);
# my ($d)=$f=~/^$i1$r$ss(.*?)$ss$/m or next;
my ($d)=$f=~/^$ss$i1$ss$r$ss(.*?)$ss$/m or (ref($y) && next);
($d=~s/^\"//) && ($d=~s/\"$//);
next if($x{$m} eq $d);
if(!ref($y)){
next if($m ne $xrfont);
&{$_->[4]}(my $x=$x{$m});
_conffile($fn,$_->[1],$_->[2],$i,$x,$_->[6]) && &{$_->[5]}();
next;
}
# collect all
#_conffile("$ENV{HOME}/.Xresources",':',': ',$m,$d,'');
$x{$m}=$d;
if(defined($x{$m})){
# or not collect?
_conffile("$ENV{HOME}/.Xresources",':',': ',$m,$d);
$m=quotemeta($m);
$s=~s/^($m:$ss).*?$/$1$d/gm;
}else{
$s.="$m: $d\n";
}
}
}
if(_mparam(@_)){
return if("@_" eq '-'); # todo: run menu here to resize panel
return if(exists($exec{$_[1]}) && ($_[1]=&{$exec{$_[1]}}()) eq '');
$x{$_[0]}=$_[1];
my $s;
my $i="$_[0]: $_[1]";
my $x=exists($auto{$i})?&{$auto{$i}}():$_[1];
$s=_xrdb($_,$_ eq $_[0]?$_[1]:$x) for($_[0],@{$cname{$_[0]}});
for(@xfiles){
my ($x,$y)=@_;
&{$_->[3]}($x) || next;
&{$_->[4]}($y);
_conffile("$ENV{HOME}/$_->[0]",$_->[1],$_->[2],$x,$y) && &{$_->[5]}();
}
if($i=~/^X\..*/){
exec($bin.'openbox','--restart');
}elsif($i=~/^xscreensaver/){
if($i eq 'xscreensaver.mode: off' || $i eq 'xscreensaver.mode: X'){
system($bin.'xscreensaver-command','-exit');
# xset code make dpms optional. let's separate
system($bin.'xset','-dpms');
system($bin.'xset','s','off');
}
if($x{'xscreensaver.mode'} eq 'X'){
my @t=($x{'xscreensaver.dpmsStandby'},$x{'xscreensaver.dpmsSuspend'},$x{'xscreensaver.dpmsOff'});
my @t1=($x{'xscreensaver.timeout'},$x{'xscreensaver.cycle'});
my $dpms=lc($x{'xscreensaver.dpmsEnabled'}) ne 'false';
my $dpms1=lc($x{'xscreensaver.dpmsQuickOff'}) eq 'true';
my ($h,$m,$s);
$_=(($h,$m,$s)=$_=~/^(\d+):(\d+):(\d+)$/)?$s+60*($m+60*$h):$_*60 for(@t,@t1);
for(@t){
$_=$x if($_<$x);
}
system($bin.'xset','dpms',$dpms?(@t,'+dpms'):$dpms1?(0,0,0):(@t,'-dpms'));
exec($bin.'xset','s','blank','s','expose','s',$dpms && $dpms1?'off':('on','s',@t1));
}elsif(lc($x{'xscreensaver.mode'}) ne 'off'){
system($bin.'xscreensaver-command','-restart') && exec($bin.'xscreensaver');
}
}elsif($i=~/^Xcursor\./){
system($bin.'xsetroot','-xcf','/usr/share/cursors/xorg-x11/'.$x{'Xcursor.theme'}.'/cursors/left_ptr',$x{'Xcursor.size'}) if(exists($x{'Xcursor.theme'}) && exists($x{'Xcursor.size'}) && $x{'Xcursor.theme'} ne '' && $x{'Xcursor.size'} ne '');
}
exit;
}
delete($xrparam{$_}) for($x{'xscreensaver.mode'} eq 'X'?grep(/xscreensaver\.(?!mode|dpms|timeout|cycle|lock)/,keys %xrparam):());
simple_menu(\%xrparam,\%x);
}
###########################################
for(keys %P){
&{$cmd{$_}}(@{$P{$_}}) if(exists($cmd{$_}));
}
&{$config{$_}->{${$_}}}() for(keys %config);
my @fx=('<',(glob('/usr/share/doc/openbox*/rc.xsd*'))[-1]);
@fx=('-|',"bzip2 -dc $fx[1]") if($fx[1]=~/.bz2$/);
sort_xsd($_,'ob') for(load_xml(open_($F,@fx),1));
xsd();
$type{'ob:openbox_config:theme:name'}=[(map{substr($_,18,-10)}glob('/usr/share/themes/*/openbox-3')),'/dev/null'];
$type{'ob:openbox_config:desktops:firstdesk'}=[$prop{'_NET_CURRENT_DESKTOP'}+1];
$type{'ob:openbox_config:desktops:number'}=[$prop{'_NET_NUMBER_OF_DESKTOPS'}||()];
$type{'ob:openbox_config:theme:font:name'}=\%fonts;
$type{'ob:openbox_config:theme:font:size'}=[(5..32)];
($X,$Y)=$prop{'_NET_DESKTOP_GEOMETRY'}=~/(\d+), (\d+)/;
$X||=256;
$Y||=256;
$i=0;
$type{'ob:openbox_config:margins:'.$_}=[0..8,12,16,20,24,28,32,map{$_*8}5..(($i++&1)?$Y:$X)/16] for('left','top','right','bottom');
print '';
sort_tags($_,'ob') for(load_xml(open_($F,'<',@rc),2));
if(!exists($P{id})){
print '';
for(keys %config){
$x=1;
my @a;
for my $i (@a=keys %{$config{$_}}){
last if($i eq ${$_});
$x++;
}
push @a,$a[0];
print "- $fix "$a[$x] " '<!-- ob3menuconfig:$_:' ${$_}
";
}
for(sort keys %menu){
$_=~/:/ and next;
$conf++;
print "";
}
}
print '';