MiniCurses.pm 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. # ex:ts=8 sw=4:
  2. # $OpenBSD: MiniCurses.pm,v 1.12 2016/06/28 15:28:20 espie Exp $
  3. #
  4. # Copyright (c) 2010-2013 Marc Espie <espie@openbsd.org>
  5. #
  6. # Permission to use, copy, modify, and distribute this software for any
  7. # purpose with or without fee is hereby granted, provided that the above
  8. # copyright notice and this permission notice appear in all copies.
  9. #
  10. # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  11. # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  12. # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  13. # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  14. # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  15. # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  16. # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  17. use strict;
  18. use warnings;
  19. package DPB::MiniCurses;
  20. use Term::Cap;
  21. use Term::ReadKey;
  22. use constant {
  23. BLACK => 0,
  24. RED => 1,
  25. GREEN => 2,
  26. YELLOW => 3,
  27. BLUE => 4,
  28. PURPLE => 5,
  29. TURQUOISE => 6,
  30. WHITE => 7 };
  31. sub term_send
  32. {
  33. my ($self, $seq) = @_;
  34. $self->{terminal}->Tputs($seq, 1, \*STDOUT);
  35. }
  36. sub refresh
  37. {
  38. my $self = shift;
  39. $self->{write} = 'go_write_home';
  40. $self->{force} = 1;
  41. }
  42. sub handle_window
  43. {
  44. my $self = shift;
  45. $self->refresh;
  46. }
  47. sub width
  48. {
  49. my $self = shift;
  50. return $self->{state}->width;
  51. }
  52. sub height
  53. {
  54. my $self = shift;
  55. return $self->{state}->height;
  56. }
  57. sub create_terminal
  58. {
  59. my $self = shift;
  60. my $oldfh = select(STDOUT);
  61. $| = 1;
  62. # XXX go back to totally non-buffered raw shit
  63. binmode(STDOUT, ':pop');
  64. select($oldfh);
  65. use POSIX;
  66. my $termios = POSIX::Termios->new;
  67. $termios->getattr(0);
  68. $self->{terminal} = Term::Cap->Tgetent({ OSPEED =>
  69. $termios->getospeed });
  70. $self->{home} = $self->{terminal}->Tputs("ho", 1);
  71. $self->{clear} = $self->{terminal}->Tputs("cl", 1);
  72. $self->{down} = $self->{terminal}->Tputs("do", 1);
  73. $self->{glitch} = exists $self->{terminal}{_xn};
  74. $self->{cleareol} = $self->{terminal}->Tputs("ce", 1);
  75. if ($self->{state}{color}) {
  76. $self->{bg} = $self->{terminal}->Tputs('AB', 1);
  77. $self->{fg} = $self->{terminal}->Tputs('AF', 1);
  78. $self->{blink} = $self->{terminal}->Tputs('mb', 1);
  79. $self->{dontblink} = $self->{terminal}->Tputs('me', 1);
  80. }
  81. if ($self->{state}{nocursor}) {
  82. $self->{invisible} =
  83. $self->{terminal}->Tputs("vi", 1);
  84. $self->{visible} =
  85. $self->{terminal}->Tputs("ve", 1);
  86. }
  87. if ($self->{home}) {
  88. $self->{write} = "go_write_home";
  89. } else {
  90. $self->{write} = "write_clear";
  91. }
  92. }
  93. sub write_clear
  94. {
  95. my ($self, $msg) = @_;
  96. my $r = $self->{clear};
  97. $self->{oldlines} = [$self->cut_lines($msg)];
  98. my $n = 2;
  99. for my $line (@{$self->{oldlines}}) {
  100. last if $n++ > $self->height;
  101. $r .= $self->clamped($line);
  102. }
  103. print $r;
  104. }
  105. sub cut_lines
  106. {
  107. my ($self, $msg) = @_;
  108. my @lines = ();
  109. for my $line (split("\n", $msg)) {
  110. while (length $line > $self->width) {
  111. push(@lines, substr($line, 0, $self->width));
  112. $line = substr($line, $self->width);
  113. }
  114. push(@lines, $line);
  115. }
  116. return @lines;
  117. }
  118. sub default_fg
  119. {
  120. my ($self, $color) = @_;
  121. $self->{resetfg} = sprintf($self->{fg}, $color);
  122. }
  123. sub default_bg
  124. {
  125. my ($self, $color) = @_;
  126. $self->{resetbg} = sprintf($self->{bg}, $color);
  127. }
  128. sub color
  129. {
  130. my ($self, $expr, $color) = @_;
  131. return sprintf($self->{fg}, $color).$expr.$self->{resetfg};
  132. }
  133. sub bg
  134. {
  135. my ($self, $expr, $color) = @_;
  136. return sprintf($self->{bg}, $color).$expr.$self->{resetbg};
  137. }
  138. sub blink
  139. {
  140. my ($self, $expr, $color) = @_;
  141. return $self->{blink}.$expr.$self->{dontblink};
  142. }
  143. sub mogrify
  144. {
  145. my ($self, $line) = @_;
  146. my $percent = PURPLE;
  147. $self->default_bg(BLACK);
  148. $self->default_fg(WHITE);
  149. if ($line =~ m/waiting-for-lock/) {
  150. $line = $self->color($line, BLUE);
  151. $self->default_fg(BLUE);
  152. } elsif ($line =~ m/frozen/) {
  153. if ($line =~ m/for\s+\d+\s*(mn|HOURS)/) {
  154. $line = $self->bg($self->color($line, BLACK), RED);
  155. $self->default_bg(RED);
  156. $self->default_fg(BLACK);
  157. $percent = WHITE;
  158. } else {
  159. $line = $self->color($line, RED);
  160. $self->default_fg(RED);
  161. }
  162. } elsif ($line =~ m/^\</) {
  163. $line = $self->color($line, TURQUOISE);
  164. $self->default_fg(TURQUOISE);
  165. } elsif ($line =~ m/^(LISTING|UPDATING)/) {
  166. $line = $self->bg($self->color($line, WHITE), BLUE);
  167. $self->default_bg(BLUE);
  168. $self->default_fg(WHITE);
  169. } elsif ($line =~ m/^I=/) {
  170. $line = $self->bg($self->color($line, WHITE), BLUE);
  171. } elsif ($line =~ m/^E=/) {
  172. $line = $self->color($line, RED);
  173. $self->default_fg(RED);
  174. } elsif ($line =~ m/^Hosts:/) {
  175. $line =~ s/([\@\w\.\-]*[\@\w.])(\s|\(|$)/$self->color($1, RED).$2/ge;
  176. $line =~ s/([\@\w\.\-]+\-)(\s|\(|$)/$self->blink($self->bg($self->color($1, BLACK), RED)).$2/ge;
  177. $line =~ s/(^Hosts:)/$self->color($1, BLUE)/ge;
  178. }
  179. $line =~ s/(\[\d+\])/$self->color($1, GREEN)/ge;
  180. $line =~ s/(\(.*?\))/$self->color($1, YELLOW)/ge;
  181. $line =~ s/(\d+\%)/$self->color($1, $percent)/ge;
  182. return $line;
  183. }
  184. sub clamped
  185. {
  186. my ($self, $line) = @_;
  187. my $l2 = $line;
  188. if (defined $self->{fg}) {
  189. $l2 = $self->mogrify($l2);
  190. }
  191. if (!$self->{glitch} && length $line == $self->width) {
  192. return $l2;
  193. } else {
  194. return $l2."\n";
  195. }
  196. }
  197. sub clear_clamped
  198. {
  199. my ($self, $line) = @_;
  200. my $l2 = $line;
  201. if (defined $self->{fg}) {
  202. $l2 = $self->mogrify($l2);
  203. }
  204. if (!$self->{glitch} && length $line == $self->width) {
  205. return $l2;
  206. } else {
  207. return $self->{cleareol}.$l2."\n";
  208. }
  209. }
  210. sub do_line
  211. {
  212. my ($self, $new, $old) = @_;
  213. # line didn't change: try to go down
  214. if (defined $old && $old eq $new) {
  215. if ($self->{down}) {
  216. return $self->{down};
  217. }
  218. }
  219. # adjust newline to correct length
  220. if (defined $old && (length $old) > (length $new)) {
  221. if ($self->{cleareol}) {
  222. return $self->clear_clamped($new);
  223. }
  224. $new .= " "x ((length $old) - (length $new));
  225. }
  226. return $self->clamped($new);
  227. }
  228. sub lines
  229. {
  230. my ($self, @new) = @_;
  231. my $n = 2;
  232. my $r = '';
  233. while (@new > 0) {
  234. return $r if $n++ > $self->height;
  235. $r .= $self->do_line(shift @new, shift @{$self->{oldlines}});
  236. }
  237. # extra lines must disappear
  238. while (@{$self->{oldlines}} > 0) {
  239. my $line = shift @{$self->{oldlines}};
  240. if ($self->{cleareol}) {
  241. $r .= $self->clear_clamped('');
  242. } else {
  243. $line = " "x (length $line);
  244. $r .= $self->clamped($line);
  245. }
  246. last if $n++ > $self->height;
  247. }
  248. return $r;
  249. }
  250. sub write_home
  251. {
  252. my ($self, $msg) = @_;
  253. my @new = $self->cut_lines($msg);
  254. print $self->{home}.$self->lines(@new);
  255. $self->{oldlines} = \@new;
  256. }
  257. sub go_write_home
  258. {
  259. # first report has to clear the screen
  260. my ($self, $msg) = @_;
  261. $self->write_clear($msg);
  262. $self->{write} = 'write_home';
  263. }
  264. 1;