#!/usr/bin/perl
use strict;
use X11::Protocol;
use IO::Select;
use Time::HiRes 'gettimeofday';
sub min { $_[0] <= $_[1] ? $_[0] : $_[1] }
sub max { $_[0] >= $_[1] ? $_[0] : $_[1] }
my $X = new X11::Protocol;
$X->init_extension("RENDER") or die "The Render extension is required";
my($rgba32, $screen_fmt);
my($formats, $screens,) = $X->RenderQueryPictFormats();
for my $f (@$formats) {
$rgba32 = $f->[0] if $f->[2] == 32 and $f->[3] == 16 and $f->[5] == 8
and $f->[7] == 0 and $f->[9] == 24;
}
for my $s (@$screens) {
my @s = @$s;
shift @s;
for my $d (@s) {
my @d = @$d;
next unless shift(@d) == $X->root_depth;
for my $v (@d) {
if ($v->[0] == $X->root_visual) {
$screen_fmt = $v->[1];
}
}
}
}
my $size = 70;
my($width_fact, $radius, $tick_size, $depth);
use constant PI => 4*atan2(1,1);
sub tri_to_traps {
my($x1, $y1, $x2, $y2, $x3, $y3) = @_;
my @points = ([$x1, $y1], [$x2, $y2], [$x3, $y3]);
@points = sort {$a->[1] <=> $b->[1]} @points;
($x1, $y1, $x2, $y2, $x3, $y3) =
(@{$points[0]}, @{$points[1]}, @{$points[2]});
my($trap1, $trap2);
if (($x2-$x1)*($y3-$y1) < ($x3-$x1)*($y2-$y1)) {
$trap1 = [$y1, $y2, ($x1, $y1), ($x2, $y2), ($x1, $y1), ($x3, $y3)];
$trap2 = [$y2, $y3, ($x2, $y2), ($x3, $y3), ($x1, $y1), ($x3, $y3)];
} else {
$trap1 = [$y1, $y2, ($x1, $y1), ($x3, $y3), ($x1, $y1), ($x2, $y2)],
$trap2 = [$y2, $y3, ($x1, $y1), ($x3, $y3), ($x2, $y2), ($x3, $y3)];
}
return ($trap1, $trap2);
}
sub render_tri {
my($op, $src_pict, $src_x, $src_y, $dst_pict, $mask, $tri) = @_;
my($trap1, $trap2) = tri_to_traps(@$tri);
$X->RenderTrapezoids($op, $src_pict, $src_x, $src_y, $dst_pict,
$mask, $trap1, $trap2);
# $X->RenderTriangles($op, $src_pict, $src_x, $src_y, $dst_pict, $mask,
# $tri);
}
sub render_quad {
my($op, $src_pict, $src_x, $src_y, $dst_pict, $mask, @points) = @_;
render_tri($op, $src_pict, $src_x, $src_y, $dst_pict, $mask,
[@points[0,1, 2,3, 4,5]]);
render_tri($op, $src_pict, $src_x, $src_y, $dst_pict, $mask,
[@points[0,1, 4,5, 6,7]]);
}
sub polar2rect {
my($r, $theta) = @_;
my $x = $size/2 + $r * sin($theta);
my $y = $size/2 - $r * cos($theta);
return ($x, $y);
}
my $win = $X->new_rsrc;
$X->CreateWindow($win, $X->root, 'InputOutput', $X->root_depth,
'CopyFromParent', (0, 0), $size, $size, 0,
'background_pixel' => $X->white_pixel,
'event_mask' =>
$X->pack_event_mask('Exposure', 'KeyPress', 'ButtonRelease',
'StructureNotify'));
$X->ChangeProperty($win, $X->atom('WM_ICON_NAME'),
$X->atom('STRING'), 8, 'Replace', "render-clock");
$X->ChangeProperty($win, $X->atom('WM_NAME'), $X->atom('STRING'),
8, 'Replace', "Rendered Clock");
$X->ChangeProperty($win, $X->atom('WM_NORMAL_HINTS'),
$X->atom('WM_SIZE_HINTS'), 32, 'Replace',
pack("Lx40llllx12", 128, 1, 1, 1, 1));
$X->ChangeProperty($win, $X->atom('WM_HINTS'), $X->atom('WM_HINTS'),
32, 'Replace', pack("IIIx24", 1|2, 1, 1));
my $delete_atom = $X->atom('WM_DELETE_WINDOW');
$X->ChangeProperty($win, $X->atom('WM_PROTOCOLS'), $X->atom('ATOM'),
32, 'Replace', pack("L", $delete_atom));
my $progname = $0;
$progname =~ s[^.*/][];
$progname = $ENV{'RESOURCE_NAME'} || $progname;
$X->ChangeProperty($win, $X->atom('WM_CLASS'), $X->atom('STRING'),
8, 'Replace', "$progname\0Render-clock");
my($tick_color, $minute_color, $hour_color, $second_color);
# Red Green Blue Opacity
# $tick_color = [0, 0, 0, 0xffff];
# $minute_color = [0xffff,0, 0, 0x8000];
# $hour_color = [0, 0xffff,0, 0x8000];
# $second_color = [0, 0, 0xffff,0x8000];
# # Red Green Blue Opacity
# $tick_color = [0, 0, 0, 0xffff];
# $minute_color = [0, 0, 0, 0x8000];
# $hour_color = [0, 0, 0, 0x8000];
# $second_color = [0, 0, 0, 0x8000];
# # Red Green Blue Opacity
# $tick_color = [0, 0, 0, 0xffff];
# $minute_color = [0, 0, 0x4fff,0x8000];
# $hour_color = [0, 0, 0x4fff,0x8000];
# $second_color = [0, 0, 0x4fff,0x8000];
# Red Green Blue Opacity
$tick_color = [0, 0, 0, 0xffff];
$minute_color = [0xffff,0, 0, 0x8000];
$hour_color = [0, 0x4fff,0, 0x8000];
$second_color = [0, 0, 0x4fff,0x8000];
my($face_pixmap, $face_pict);
my $black_pixmap = $X->new_rsrc;
$X->CreatePixmap($black_pixmap, $win, 32, 1, 1);
my $black_pict = $X->new_rsrc;
$X->RenderCreatePicture($black_pict, $black_pixmap, $rgba32, 'repeat' => 1);
$X->RenderFillRectangles('Src', $black_pict, $tick_color, [0, 0, 1, 1]);
my $red_pixmap = $X->new_rsrc;
$X->CreatePixmap($red_pixmap, $win, 32, 1, 1);
my $red_pict = $X->new_rsrc;
$X->RenderCreatePicture($red_pict, $red_pixmap, $rgba32, 'repeat' => 1);
$X->RenderFillRectangles('Src', $red_pict, $minute_color, [0, 0, 1, 1]);
my $green_pixmap = $X->new_rsrc;
$X->CreatePixmap($green_pixmap, $win, 32, 1, 1);
my $green_pict = $X->new_rsrc;
$X->RenderCreatePicture($green_pict, $green_pixmap, $rgba32, 'repeat' => 1);
$X->RenderFillRectangles('Src', $green_pict, $hour_color, [0, 0, 1, 1]);
my $blue_pixmap = $X->new_rsrc;
$X->CreatePixmap($blue_pixmap, $win, 32, 1, 1);
my $blue_pict = $X->new_rsrc;
$X->RenderCreatePicture($blue_pict, $blue_pixmap, $rgba32, 'repeat' => 1);
$X->RenderFillRectangles('Src', $blue_pict, $second_color, [0, 0, 1, 1]);
my $hilite_pixmap = $X->new_rsrc;
$X->CreatePixmap($hilite_pixmap, $win, 32, 1, 1);
my $hilite_pict = $X->new_rsrc;
$X->RenderCreatePicture($hilite_pict, $hilite_pixmap, $rgba32, 'repeat' => 1);
my($buffer_pixmap, $buffer_pict);
sub setup_face {
$width_fact = 2;
$radius = 0.475 * $size;
$tick_size = $size / 10;
$depth = $size / 150;
if ($face_pixmap) {
$X->FreePixmap($face_pixmap);
$X->RenderFreePicture($face_pict);
} else {
$face_pixmap = $X->new_rsrc;
$face_pict = $X->new_rsrc;
}
$X->CreatePixmap($face_pixmap, $win, 32, $size, $size);
$X->RenderCreatePicture($face_pict, $face_pixmap, $rgba32,
'poly_edge' => 'Smooth', 'poly_mode' => 'Precise');
$X->RenderFillRectangles('Src', $face_pict, [0xefff,0xefff,0xefff,0xffff],
[0, 0, $size, $size]);
for my $tick (0 .. 59) {
my $theta = $tick/30 * PI;
my $size_outer = 0.01;
my $inner_rad;
if ($tick % 5) {
$inner_rad = $radius - $tick_size/2;
} else {
$inner_rad = $radius - $tick_size;
}
my $size_inner = $size_outer * ($radius/$inner_rad);
my($x1, $y1) = polar2rect($radius, $theta - $size_outer);
my($x2, $y2) = polar2rect($radius, $theta + $size_outer);
my($x3, $y3) = polar2rect($inner_rad, $theta + $size_inner);
my($x4, $y4) = polar2rect($inner_rad, $theta - $size_inner);
render_quad('Over', $black_pict, $size, $size, $face_pict, 'None',
($x1, $y1), ($x2, $y2), ($x3, $y3), ($x4, $y4));
}
#$X->RenderFillRectangles('Over', $face_pict, [0,0,0,0xffff],
# [$size/2-5, $size/2-5, 10, 10]);
if ($buffer_pixmap) {
$X->FreePixmap($buffer_pixmap);
$X->RenderFreePicture($buffer_pict);
} else {
$buffer_pixmap = $X->new_rsrc;
$buffer_pict = $X->new_rsrc;
}
$X->CreatePixmap($buffer_pixmap, $win, $X->root_depth, $size, $size);
$X->RenderCreatePicture($buffer_pict, $buffer_pixmap, $screen_fmt,
'poly_edge' => 'Smooth', 'poly_mode' => 'Precise');
}
setup_face();
my $copy_gc = $X->new_rsrc;
$X->CreateGC($copy_gc, $win);
$X->MapWindow($win);
sub draw_hand {
my($pict, $x1, $y1, $x2, $y2, $x3, $y3, $x4, $y4) = @_;
my @p = ([$x1, $y1], [$x2, $y2], [$x3, $y3], [$x4, $y4]);
my @ip;
$#ip = $#p;
for my $j (-2 .. $#p - 2) {
my($ix, $iy) = ($p[$j+1][0] - $p[$j][0], $p[$j+1][1] - $p[$j][1]);
my($ox, $oy) = ($p[$j+2][0] - $p[$j+1][0], $p[$j+2][1] - $p[$j+1][1]);
if ($ix*$oy > $iy*$ox) {
$ix = -$ix; $iy = -$iy;
} else {
$ox = -$ox; $oy = -$oy;
}
my($in) = sqrt($ix*$ix + $iy*$iy); $ix /= $in; $iy /= $in;
my($on) = sqrt($ox*$ox + $oy*$oy); $ox /= $on; $oy /= $on;
my($mx, $my) = (($ix + $ox)/2, ($iy + $oy)/2);
my($mn) = max(abs($mx), abs($my)); $mx /= $mn; $my /= $mn;
$ip[$j+1][0] = $p[$j+1][0] + $depth * $mx;
$ip[$j+1][1] = $p[$j+1][1] + $depth * $my;
}
render_quad('Over', $pict, $size, $size, $buffer_pict, 'None',
($x1, $y1), ($x2, $y2), ($x3, $y3), ($x4, $y4));
for my $j (-1 .. $#p - 1) {
my $angle = atan2($p[$j+1][1]-$p[$j][1], $p[$j+1][0]-$p[$j][0]);
my $gray = 0x8000 + 0x4000 * sin($angle + 3*PI / 4);
my $alpha = 0.5;
$X->RenderFillRectangles('Src', $hilite_pict,
[$gray, $gray, $gray,
$alpha*0xffff],
[0, 0, 1, 1]);
render_quad('Over', $hilite_pict, $size, $size, $buffer_pict, 'None',
@{$p[$j]}, @{$ip[$j]}, @{$ip[$j + 1]}, @{$p[$j + 1]});
}
}
sub draw {
$X->RenderFillRectangles('Src', $buffer_pict,
[0xffff, 0xffff, 0xffff, 0xffff],
[0, 0, $size, $size]);
$X->RenderComposite('Over', $face_pict, 'None', $buffer_pict, 0, 0,
0, 0, 0, 0, $size, $size);
my($unix_time, $microsec) = gettimeofday();
my($sec, $min, $hour) = localtime($unix_time);
$sec += $microsec / 1_000_000;
{
my $hour_theta = ($hour % 12 + $min/60 + $sec/3600)/6 * PI;
my $hour_size_outer = 0.04 * $width_fact;
my $hour_size_inner = $hour_size_outer * (.6/.3) * 1.4;
my($x1, $y1) = polar2rect(.6*$radius, $hour_theta - $hour_size_outer);
my($x2, $y2) = polar2rect(.6*$radius, $hour_theta + $hour_size_outer);
my($x3, $y3) = polar2rect(-.3*$radius, $hour_theta - $hour_size_inner);
my($x4, $y4) = polar2rect(-.3*$radius, $hour_theta + $hour_size_inner);
draw_hand($green_pict,
($x1, $y1), ($x2, $y2), ($x3, $y3), ($x4, $y4));
}
{
my $min_theta = ($min + $sec/60)/30 * PI;
my $min_size_outer = 0.02 * $width_fact;
my $min_size_inner = $min_size_outer * (.8/.2) * 1.3;
my($x1, $y1) = polar2rect(.8*$radius, $min_theta - $min_size_outer);
my($x2, $y2) = polar2rect(.8*$radius, $min_theta + $min_size_outer);
my($x3, $y3) = polar2rect(-.2*$radius, $min_theta - $min_size_inner);
my($x4, $y4) = polar2rect(-.2*$radius, $min_theta + $min_size_inner);
draw_hand($red_pict,
($x1, $y1), ($x2, $y2), ($x3, $y3), ($x4, $y4));
}
{
my $sec_theta = $sec/30 * PI;
my $sec_size_outer = 0.01 * $width_fact;
my $sec_size_inner = $sec_size_outer * (.95/.15) * 1.3;
my($x1, $y1) = polar2rect(.95*$radius, $sec_theta - $sec_size_outer);
my($x2, $y2) = polar2rect(.95*$radius, $sec_theta + $sec_size_outer);
my($x3, $y3) = polar2rect(-.15*$radius, $sec_theta - $sec_size_inner);
my($x4, $y4) = polar2rect(-.15*$radius, $sec_theta + $sec_size_inner);
draw_hand($blue_pict,
($x1, $y1), ($x2, $y2), ($x3, $y3), ($x4, $y4));
}
$X->CopyArea($buffer_pixmap, $win, $copy_gc,
0, 0, $size, $size, 0, 0);
}
$X->event_handler('queue');
my $fds = IO::Select->new($X->connection->fh);
my $start_time = time;
my $sample_time = Time::HiRes::time;
my $frames = 0;
my $delay = 0.00001;
for (;;) {
$X->flush();
$X->GetScreenSaver(); # AKA XSync()
#$X->handle_input if $fds->can_read(0);
Time::HiRes::sleep(0.01 + $delay);
my %e;
while (%e = $X->dequeue_event) {
if ($e{'name'} eq "Expose") {
draw();
} elsif ($e{'name'} eq "ButtonRelease"
or $e{'name'} eq "KeyPress") {
exit;
} elsif ($e{'name'} eq "ConfigureNotify") {
my($w, $h) = @e{'width', 'height'};
$size = min($w, $h);
setup_face();
$frames = 0;
$start_time = time;
$sample_time = Time::HiRes::time;
} elsif ($e{'name'} eq "ClientMessage"
and unpack("L", $e{'data'}) == $delete_atom) {
exit;
}
}
draw();
$frames++;
if (!($frames % 20)) {
my $fps = $frames/(Time::HiRes::time-$sample_time);
#print "$fps FPS delay $delay\n";
if ($fps > 30) {
$delay = 0.75 * $delay + 0.25 * ($delay + 1/30 - 1/$fps);
} elsif ($fps < 30) {
$delay = 0.75 * $delay;
}
}
}
|