1a5ae21e4Smrg#!/usr/bin/env perl
25104ee6eSmrg# $XTermId: titlestack.pl,v 1.35 2024/11/29 01:09:46 tom Exp $
3a5ae21e4Smrg# -----------------------------------------------------------------------------
4a5ae21e4Smrg# this file is part of xterm
5a5ae21e4Smrg#
65104ee6eSmrg# Copyright 2019,2024 by Thomas E. Dickey
7a5ae21e4Smrg#
8a5ae21e4Smrg#                         All Rights Reserved
9a5ae21e4Smrg#
10a5ae21e4Smrg# Permission is hereby granted, free of charge, to any person obtaining a
11a5ae21e4Smrg# copy of this software and associated documentation files (the
12a5ae21e4Smrg# "Software"), to deal in the Software without restriction, including
13a5ae21e4Smrg# without limitation the rights to use, copy, modify, merge, publish,
14a5ae21e4Smrg# distribute, sublicense, and/or sell copies of the Software, and to
15a5ae21e4Smrg# permit persons to whom the Software is furnished to do so, subject to
16a5ae21e4Smrg# the following conditions:
17a5ae21e4Smrg#
18a5ae21e4Smrg# The above copyright notice and this permission notice shall be included
19a5ae21e4Smrg# in all copies or substantial portions of the Software.
20a5ae21e4Smrg#
21a5ae21e4Smrg# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22a5ae21e4Smrg# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23a5ae21e4Smrg# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
24a5ae21e4Smrg# IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY
25a5ae21e4Smrg# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
26a5ae21e4Smrg# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
27a5ae21e4Smrg# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28a5ae21e4Smrg#
29a5ae21e4Smrg# Except as contained in this notice, the name(s) of the above copyright
30a5ae21e4Smrg# holders shall not be used in advertising or otherwise to promote the
31a5ae21e4Smrg# sale, use or other dealings in this Software without prior written
32a5ae21e4Smrg# authorization.
33a5ae21e4Smrg# -----------------------------------------------------------------------------
34a5ae21e4Smrg# Test the title-stack and title-mode options of xterm.
35a5ae21e4Smrg
36a5ae21e4Smrg# TODO: add test for arbitrary x property
37a5ae21e4Smrg# TODO: allow -g and -v options to toggle interactively
38a5ae21e4Smrg
39a5ae21e4Smrguse strict;
40a5ae21e4Smrguse warnings;
41a5ae21e4Smrg
42a5ae21e4Smrguse Getopt::Std;
43a5ae21e4Smrguse Encode qw(decode encode);
44a5ae21e4Smrguse Term::ReadKey;
45a5ae21e4Smrguse I18N::Langinfo qw(langinfo CODESET);
46a5ae21e4Smrg
47a5ae21e4Smrgour $target = "";
48a5ae21e4Smrg
49a5ae21e4Smrgour $encoding = lc( langinfo( CODESET() ) );
50a5ae21e4Smrgour $wm_name;
515104ee6eSmrgour ( $opt_b, $opt_c, $opt_g, $opt_l, $opt_s, $opt_v, $opt_8 );
52a5ae21e4Smrg
53a5ae21e4Smrgour @titlestack;    # stack of title-strings, using current encoding
54a5ae21e4Smrgour @item_stack;    # selector used when doing a push
55a5ae21e4Smrgour @mode_stack;    # titleModes in effect when titlestack was loaded
56a5ae21e4Smrgour $SP;            # stack-pointer
575104ee6eSmrgour $SQ = 10;       # stack-limit
58a5ae21e4Smrgour $TM;            # current titleModes, in various combinations
595104ee6eSmrgour @cmd_buffer;    # command-input
605104ee6eSmrgour $cmd_index;     # current index in $cmd_buffer[]
615104ee6eSmrgour $log_fp;        # logging-output
62a5ae21e4Smrg
63a5ae21e4Smrgour $utf8_sample = 0;
64a5ae21e4Smrg
65a5ae21e4Smrgour $CSI = "\x1b[";
665104ee6eSmrgour $DCS = "\x1bP";
67a5ae21e4Smrgour $OSC = "\x1b]";
68a5ae21e4Smrgour $ST  = "\x1b\\";
69a5ae21e4Smrg
70a5ae21e4Smrgsub SendHEX()  { return ( $TM & 1 ) ? 1 : 0; }
71a5ae21e4Smrgsub ReadHEX()  { return ( $TM & 2 ) ? 1 : 0; }
72a5ae21e4Smrgsub SendUTF8() { return ( $TM & 4 ) ? 1 : 0; }
73a5ae21e4Smrgsub ReadUTF8() { return ( $TM & 8 ) ? 1 : 0; }
74a5ae21e4Smrg
75a5ae21e4Smrgsub to_hex($) {
76a5ae21e4Smrg    my $value  = shift;
77a5ae21e4Smrg    my $result = "";
78a5ae21e4Smrg    my $n;
79a5ae21e4Smrg
80a5ae21e4Smrg    for ( $n = 0 ; $n < length($value) ; ++$n ) {
81a5ae21e4Smrg        $result .= sprintf( "%02X", ord substr( $value, $n, 1 ) );
82a5ae21e4Smrg    }
83a5ae21e4Smrg    return $result;
84a5ae21e4Smrg}
85a5ae21e4Smrg
86a5ae21e4Smrgsub from_hex($) {
87a5ae21e4Smrg    my $value  = shift;
88a5ae21e4Smrg    my $result = "";
89a5ae21e4Smrg    if ( $value =~ /^[[:xdigit:]]+$/ and ( length($value) % 2 ) == 0 ) {
90a5ae21e4Smrg        my $octets = "";
91a5ae21e4Smrg        for ( my $n = 0 ; $n < length($value) ; $n += 2 ) {
92a5ae21e4Smrg            my $pair = substr( $value, $n, 2 );
93a5ae21e4Smrg            my $data = hex $pair;
94a5ae21e4Smrg            $octets .= chr($data);
95a5ae21e4Smrg        }
96a5ae21e4Smrg        $result = decode( &ReadUTF8 ? "utf-8" : "iso-8859-1", $octets );
97a5ae21e4Smrg    }
98a5ae21e4Smrg    else {
99a5ae21e4Smrg        $result = $value;
100a5ae21e4Smrg    }
101a5ae21e4Smrg    return $result;
102a5ae21e4Smrg}
103a5ae21e4Smrg
104a5ae21e4Smrgsub show_string($) {
105a5ae21e4Smrg    my $value = shift;
106a5ae21e4Smrg    my $n;
107a5ae21e4Smrg    my $octets =
108a5ae21e4Smrg      encode( ( ( $encoding eq "utf-8" ) ? "utf-8" : "iso-8859-1" ), $value );
109a5ae21e4Smrg
110a5ae21e4Smrg    my $result = "";
111a5ae21e4Smrg    for ( $n = 0 ; $n < length($octets) ; $n += 1 ) {
112a5ae21e4Smrg        my $c = ord substr( $octets, $n, 1 );
113a5ae21e4Smrg        if ( $c == ord '\\' ) {
114a5ae21e4Smrg            $result .= "\\\\";
115a5ae21e4Smrg        }
116a5ae21e4Smrg        elsif ( $c == 0x1b ) {
117a5ae21e4Smrg            $result .= "\\E";
118a5ae21e4Smrg        }
119a5ae21e4Smrg        elsif ( $c == 0x7f ) {
120a5ae21e4Smrg            $result .= "^?";
121a5ae21e4Smrg        }
122a5ae21e4Smrg        elsif ( $c == 32 ) {
123a5ae21e4Smrg            $result .= "\\s";
124a5ae21e4Smrg        }
125a5ae21e4Smrg        elsif ( $c < 32 ) {
126a5ae21e4Smrg            $result .= sprintf( "^%c", $c + 64 );
127a5ae21e4Smrg        }
128a5ae21e4Smrg        elsif ( $c > 128 ) {
129a5ae21e4Smrg            $result .= sprintf( "\\%03o", $c );
130a5ae21e4Smrg        }
131a5ae21e4Smrg        else {
132a5ae21e4Smrg            $result .= chr($c);
133a5ae21e4Smrg        }
134a5ae21e4Smrg    }
135a5ae21e4Smrg
136a5ae21e4Smrg    printf "%s\r\n", $result;
137a5ae21e4Smrg}
138a5ae21e4Smrg
139a5ae21e4Smrgsub send_command($) {
140a5ae21e4Smrg    my $command = shift;
141a5ae21e4Smrg    if ($opt_v) {
142a5ae21e4Smrg        printf "send: ";
143a5ae21e4Smrg        &show_string($command);
144a5ae21e4Smrg    }
145a5ae21e4Smrg    print STDERR encode( &SendUTF8 ? "utf-8" : "iso-8859-1", $command );
146a5ae21e4Smrg}
147a5ae21e4Smrg
148a5ae21e4Smrgsub get_reply($) {
149a5ae21e4Smrg    my $command = shift;
150a5ae21e4Smrg    my $reply   = "";
151a5ae21e4Smrg
152a5ae21e4Smrg    &send_command($command);
153a5ae21e4Smrg    my $start = time;
154a5ae21e4Smrg    while (1) {
155a5ae21e4Smrg        my $test = ReadKey 1;
156a5ae21e4Smrg        last if not defined $test;
157a5ae21e4Smrg        last if ( time > ( $start + 1 ) );
158a5ae21e4Smrg
159a5ae21e4Smrg        $reply .= $test;
160a5ae21e4Smrg    }
161a5ae21e4Smrg    if ($opt_v) {
162a5ae21e4Smrg        printf "read: ";
163a5ae21e4Smrg        &show_string($reply);
164a5ae21e4Smrg    }
165a5ae21e4Smrg    return $reply;
166a5ae21e4Smrg}
167a5ae21e4Smrg
1685104ee6eSmrgsub get_level() {
1695104ee6eSmrg    my $reply = &get_reply( sprintf( "%s#S", $CSI ) );
1705104ee6eSmrg    if ( index( $reply, $CSI ) == 0 ) {
1715104ee6eSmrg        $reply = substr( $reply, length($CSI) );
1725104ee6eSmrg        if ( $reply =~ /^\d+;\d+#S$/ ) {
1735104ee6eSmrg            $reply =~ s/#S//;
1745104ee6eSmrg            my @params = split /;/, $reply;
1755104ee6eSmrg            $SP = $params[0];
1765104ee6eSmrg            $SQ = $params[1];
1775104ee6eSmrg        }
1785104ee6eSmrg    }
1795104ee6eSmrg}
1805104ee6eSmrg
1815104ee6eSmrgsub get_titlemodes() {
1825104ee6eSmrg    my $reply  = &get_reply( sprintf( "%s\$q>t%s", $DCS, $ST ) );
1835104ee6eSmrg    my $prefix = "${DCS}1\$r";
1845104ee6eSmrg    my $p      = index( $reply, $prefix );
1855104ee6eSmrg    my $q      = index( $reply, $ST );
1865104ee6eSmrg    my $r      = length($reply) - length($ST);
1875104ee6eSmrg    if ( $p == 0 and $q == $r ) {
1885104ee6eSmrg        $reply = substr( $reply, 0, $q );
1895104ee6eSmrg        $reply = substr( $reply, length($prefix) );
1905104ee6eSmrg        if ( $reply =~ /^>(\d;)*\dt$/ ) {
1915104ee6eSmrg            $reply =~ s/^.(.+).$/$1/;
1925104ee6eSmrg            my @modes = split /;/, $reply;
1935104ee6eSmrg            $TM = 0;
1945104ee6eSmrg            for my $n ( 0 .. $#modes ) {
1955104ee6eSmrg                $TM += ( 1 << $n ) if ( $modes[$n] != 0 );
1965104ee6eSmrg            }
1975104ee6eSmrg        }
1985104ee6eSmrg    }
1995104ee6eSmrg}
2005104ee6eSmrg
201a5ae21e4Smrgsub get_title($) {
202a5ae21e4Smrg    my $icon   = shift;
203a5ae21e4Smrg    my $reply  = &get_reply( sprintf( "%s%dt", $CSI, $icon ? 20 : 21 ) );
204a5ae21e4Smrg    my $prefix = $icon ? "L" : "l";
205a5ae21e4Smrg
206a5ae21e4Smrg    if ( $opt_8 and ( $reply =~ /^$CSI/ ) ) {
207a5ae21e4Smrg        $reply =~ s/^${CSI}//;
208a5ae21e4Smrg        $reply =~ s/${ST}$//;
209a5ae21e4Smrg    }
210a5ae21e4Smrg    else {
211a5ae21e4Smrg        $reply =~ s/^\x1b//;
212a5ae21e4Smrg        $reply =~ s/^[\[\]]//;
213a5ae21e4Smrg        if ( index( $reply, $ST ) >= 0 ) {
214a5ae21e4Smrg            $reply =~ s/\x1b\\$//;
215a5ae21e4Smrg        }
216a5ae21e4Smrg        else {
217a5ae21e4Smrg            $reply =~ s/\007$//;
218a5ae21e4Smrg        }
219a5ae21e4Smrg    }
220a5ae21e4Smrg    if ( $reply =~ /^$prefix/ ) {
221a5ae21e4Smrg        $reply =~ s/^$prefix//;
222a5ae21e4Smrg        if (&ReadHEX) {
223a5ae21e4Smrg            $reply = &from_hex($reply);
224a5ae21e4Smrg        }
225a5ae21e4Smrg    }
226a5ae21e4Smrg    else {
227a5ae21e4Smrg        $reply = "?" . $reply;
228a5ae21e4Smrg    }
229a5ae21e4Smrg    return $reply;
230a5ae21e4Smrg}
231a5ae21e4Smrg
232a5ae21e4Smrgsub raw() {
233a5ae21e4Smrg    ReadMode 'ultra-raw', 'STDIN';    # allow single-character inputs
234a5ae21e4Smrg}
235a5ae21e4Smrg
236a5ae21e4Smrgsub cooked() {
237a5ae21e4Smrg    ReadMode 'normal';
238a5ae21e4Smrg}
239a5ae21e4Smrg
2405104ee6eSmrgsub get_cmd() {
2415104ee6eSmrg    my $result;
2425104ee6eSmrg    select( undef, undef, undef, $opt_s );
2435104ee6eSmrg    if ( $cmd_index <= $#cmd_buffer ) {
2445104ee6eSmrg        $result = $cmd_buffer[ $cmd_index++ ];
2455104ee6eSmrg    }
2465104ee6eSmrg    else {
2475104ee6eSmrg        $result = "q";
2485104ee6eSmrg    }
2495104ee6eSmrg    return $result;
2505104ee6eSmrg}
2515104ee6eSmrg
2525104ee6eSmrgsub get_char() {
2535104ee6eSmrg    my $result;
2545104ee6eSmrg    if ($opt_c) {
2555104ee6eSmrg        $result = &get_cmd();
2565104ee6eSmrg        if ( index( $result, "char:" ) == 0 ) {
2575104ee6eSmrg            $result = substr( $result, 5 );
2585104ee6eSmrg        }
2595104ee6eSmrg        else {
2605104ee6eSmrg            $result = "q";
2615104ee6eSmrg        }
2625104ee6eSmrg    }
2635104ee6eSmrg    else {
2645104ee6eSmrg        $result = ReadKey 0;
2655104ee6eSmrg    }
2665104ee6eSmrg    printf $log_fp "char:%s\n", $result if ($opt_l);
2675104ee6eSmrg    return $result;
2685104ee6eSmrg}
2695104ee6eSmrg
2705104ee6eSmrgsub get_line() {
2715104ee6eSmrg    my $result;
2725104ee6eSmrg    if ($opt_c) {
2735104ee6eSmrg        $result = &get_cmd();
2745104ee6eSmrg        if ( index( $result, "line:" ) == 0 ) {
2755104ee6eSmrg            $result = substr( $result, 5 );
2765104ee6eSmrg        }
2775104ee6eSmrg        else {
2785104ee6eSmrg            $result = "";
2795104ee6eSmrg        }
2805104ee6eSmrg    }
2815104ee6eSmrg    else {
2825104ee6eSmrg        &cooked;
2835104ee6eSmrg        $result = ReadLine 0;
2845104ee6eSmrg        chomp $result;
2855104ee6eSmrg        &raw;
2865104ee6eSmrg    }
2875104ee6eSmrg    printf $log_fp "line:%s\n", $result if ($opt_l);
2885104ee6eSmrg    return $result;
2895104ee6eSmrg}
2905104ee6eSmrg
291a5ae21e4Smrgsub read_cmd($) {
292a5ae21e4Smrg    my $command = shift;
293a5ae21e4Smrg    my @result;
294a5ae21e4Smrg    if ( open( my $fp, "$command |" ) ) {
295a5ae21e4Smrg        binmode( $fp, ":utf8" ) if ( $encoding eq "utf-8" );
296a5ae21e4Smrg        @result = <$fp>;
297a5ae21e4Smrg        close($fp);
298a5ae21e4Smrg        chomp @result;
299a5ae21e4Smrg    }
300a5ae21e4Smrg    return @result;
301a5ae21e4Smrg}
302a5ae21e4Smrg
303a5ae21e4Smrgsub which_modes($) {
304a5ae21e4Smrg    my $modes  = shift;
305a5ae21e4Smrg    my $result = "";
306a5ae21e4Smrg    if ( $modes & 3 ) {
307a5ae21e4Smrg        $result .= "put" if ( ( $modes & 3 ) == 1 );
308a5ae21e4Smrg        $result .= "get" if ( ( $modes & 3 ) == 2 );
309a5ae21e4Smrg        $result .= "p/q" if ( ( $modes & 3 ) == 3 );
310a5ae21e4Smrg        $result .= " hex";
311a5ae21e4Smrg    }
312a5ae21e4Smrg    if ( $modes & 12 ) {
313a5ae21e4Smrg        $modes /= 4;
314a5ae21e4Smrg        $result .= "," unless ( $result eq "" );
315a5ae21e4Smrg        $result .= "put" if ( ( $modes & 3 ) == 1 );
316a5ae21e4Smrg        $result .= "get" if ( ( $modes & 3 ) == 2 );
317a5ae21e4Smrg        $result .= "p/q" if ( ( $modes & 3 ) == 3 );
318a5ae21e4Smrg        $result .= " utf";
319a5ae21e4Smrg    }
320a5ae21e4Smrg    $result = "default" if ( $result eq "" );
321a5ae21e4Smrg    return $result;
322a5ae21e4Smrg}
323a5ae21e4Smrg
324a5ae21e4Smrgsub which_tmode($$) {
325a5ae21e4Smrg    my $set    = shift;
326a5ae21e4Smrg    my $mode   = shift;
327a5ae21e4Smrg    my $result = "";
328a5ae21e4Smrg    $result = "set window/icon labels using hexadecimal"   if ( $mode == 0 );
329a5ae21e4Smrg    $result = "query window/icon labels using hexadecimal" if ( $mode == 1 );
330a5ae21e4Smrg    $result = "set window/icon labels using UTF-8"         if ( $mode == 2 );
331a5ae21e4Smrg    $result = "query window/icon labels using UTF-8"       if ( $mode == 3 );
332a5ae21e4Smrg    $result = "do not " . $result if ( $set == 0 and $result ne "" );
333a5ae21e4Smrg    return $result;
334a5ae21e4Smrg}
335a5ae21e4Smrg
336a5ae21e4Smrgsub get_tmode($) {
337a5ae21e4Smrg    my $set    = shift;
338a5ae21e4Smrg    my $help   = 0;
339a5ae21e4Smrg    my $result = "?";
340a5ae21e4Smrg    while ( $result !~ /^[0123]$/ ) {
3415104ee6eSmrg        $result = &get_char;
342a5ae21e4Smrg        if ( $result eq "q" ) {
343a5ae21e4Smrg            $result = -1;
344a5ae21e4Smrg            last;
345a5ae21e4Smrg        }
346a5ae21e4Smrg        elsif ( $result eq "?" and not $help ) {
347a5ae21e4Smrg            for my $n ( 0 .. 3 ) {
348a5ae21e4Smrg                printf "\r\n\t%s = %s", $n, &which_tmode( $set, $n );
349a5ae21e4Smrg            }
350a5ae21e4Smrg            printf "\r\n\t:";
351a5ae21e4Smrg            $help = 1;
352a5ae21e4Smrg        }
353a5ae21e4Smrg    }
354a5ae21e4Smrg    if ( $result >= 0 ) {
355a5ae21e4Smrg        printf "[%s]\r\n\t:", &which_tmode( $set, $result );
356a5ae21e4Smrg    }
357a5ae21e4Smrg    return $result;
358a5ae21e4Smrg}
359a5ae21e4Smrg
360a5ae21e4Smrgsub which_item($) {
361a5ae21e4Smrg    my $code   = shift;
362a5ae21e4Smrg    my $result = "";
363a5ae21e4Smrg    $result = "both" if ( $code == 0 );
364a5ae21e4Smrg    $result = "icon" if ( $code == 1 );
365a5ae21e4Smrg    $result = "name" if ( $code == 2 );
366a5ae21e4Smrg    return $result;
367a5ae21e4Smrg}
368a5ae21e4Smrg
369a5ae21e4Smrgsub which_selector($) {
370a5ae21e4Smrg    my $code   = shift;
371a5ae21e4Smrg    my $result = "";
372a5ae21e4Smrg    $result = "both titles"  if ( $code == 0 );
373a5ae21e4Smrg    $result = "icon title"   if ( $code == 1 );
374a5ae21e4Smrg    $result = "window title" if ( $code == 2 );
375a5ae21e4Smrg    return $result;
376a5ae21e4Smrg}
377a5ae21e4Smrg
378a5ae21e4Smrgsub get_selector() {
379a5ae21e4Smrg    my $result = "?";
380a5ae21e4Smrg    my $help   = 0;
381a5ae21e4Smrg    printf "\t:";
382a5ae21e4Smrg    while ( $result !~ /^[012]$/ ) {
3835104ee6eSmrg        $result = &get_char;
384a5ae21e4Smrg        if ( $result eq "q" ) {
385a5ae21e4Smrg            $result = -1;
386a5ae21e4Smrg            last;
387a5ae21e4Smrg        }
388a5ae21e4Smrg        elsif ( $result eq "l" ) {
389a5ae21e4Smrg            $result = 2;
390a5ae21e4Smrg        }
391a5ae21e4Smrg        elsif ( $result eq "L" ) {
392a5ae21e4Smrg            $result = 1;
393a5ae21e4Smrg        }
394a5ae21e4Smrg        elsif ( $result eq "?" and not $help ) {
395a5ae21e4Smrg            for my $n ( 0 .. 2 ) {
396a5ae21e4Smrg                printf "\r\n\t%d = %s", $n, &which_selector($n);
397a5ae21e4Smrg            }
398a5ae21e4Smrg            printf "\r\n\t:";
399a5ae21e4Smrg            $help = 1;
400a5ae21e4Smrg        }
401a5ae21e4Smrg    }
402a5ae21e4Smrg    if ( $result >= 0 ) {
403a5ae21e4Smrg        printf "[%s]\r\n\t:", &which_selector($result);
404a5ae21e4Smrg    }
405a5ae21e4Smrg    return $result;
406a5ae21e4Smrg}
407a5ae21e4Smrg
408a5ae21e4Smrgsub display_info() {
409a5ae21e4Smrg
410a5ae21e4Smrg    # use xprop to get properties
411a5ae21e4Smrg    my $command = "xprop";
412a5ae21e4Smrg    if ( $ENV{WINDOWID} ) {
413a5ae21e4Smrg        my $windowid = $ENV{WINDOWID};
414a5ae21e4Smrg        $command .= " -id " . $windowid if ( $windowid ne "" );
415a5ae21e4Smrg    }
416a5ae21e4Smrg    else {
417a5ae21e4Smrg        printf "...xprop\r\n";
418a5ae21e4Smrg    }
419a5ae21e4Smrg    my @props = &read_cmd($command);
420a5ae21e4Smrg    for my $n ( 0 .. $#props ) {
421a5ae21e4Smrg        printf "\t%s\r\n", $props[$n]
422a5ae21e4Smrg          if ( index( $props[$n], "WM_NAME(" ) >= 0
423a5ae21e4Smrg            or index( $props[$n], "WM_ICON_NAME(" ) >= 0 );
424a5ae21e4Smrg    }
425a5ae21e4Smrg
426a5ae21e4Smrg    # use escape sequences to get corresponding information
427a5ae21e4Smrg    printf "... Icon title:%s\r\n",   &get_title(1);
428a5ae21e4Smrg    printf "... Window title:%s\r\n", &get_title(0);
429a5ae21e4Smrg
430a5ae21e4Smrg    # show title-stack (and modes used for each level)
431a5ae21e4Smrg    printf "... Modes[%s]\r\n",  &which_modes($TM);
432a5ae21e4Smrg    printf "... Stack(%d):\r\n", $SP;
433a5ae21e4Smrg    for my $n ( 0 .. $SP ) {
434a5ae21e4Smrg        printf "\t%d [%s:%s]%s\r\n", $n, &which_item( $item_stack[$n] ),
435a5ae21e4Smrg          &which_modes( $mode_stack[$n] ), $titlestack[$n];
436a5ae21e4Smrg    }
437a5ae21e4Smrg}
438a5ae21e4Smrg
439a5ae21e4Smrgsub set_titlemode($) {
440a5ae21e4Smrg    my $set  = shift;
441a5ae21e4Smrg    my $opts = "";
442a5ae21e4Smrg    my $opt;
443a5ae21e4Smrg    printf "\t:";
444a5ae21e4Smrg    while ( ( $opt = &get_tmode($set) ) >= 0 ) {
445a5ae21e4Smrg        $TM |= ( 1 << $opt ) if ($set);
446a5ae21e4Smrg        $TM &= ~( 1 << $opt ) unless ($set);
447a5ae21e4Smrg        $opts .= ";" unless ( $opts eq "" );
448a5ae21e4Smrg        $opts .= $opt;
449a5ae21e4Smrg    }
450a5ae21e4Smrg    if ( $opts ne "" ) {
451a5ae21e4Smrg        &send_command( sprintf( "%s>%s%s", $CSI, $opts, $set ? "t" : "T" ) );
452a5ae21e4Smrg    }
4535104ee6eSmrg
4545104ee6eSmrg    if ($opt_l) {
4555104ee6eSmrg        my $save = $TM;
4565104ee6eSmrg        &get_titlemodes;
4575104ee6eSmrg
4585104ee6eSmrg        if ( $TM != $save ) {
4595104ee6eSmrg            printf $log_fp "note: expected title-modes $save, got $TM\n";
4605104ee6eSmrg        }
4615104ee6eSmrg    }
462a5ae21e4Smrg}
463a5ae21e4Smrg
464a5ae21e4Smrgsub utf8_sample($) {
465a5ae21e4Smrg    my $item = shift;
466a5ae21e4Smrg    my $last = 4;
467a5ae21e4Smrg    my $text;
468a5ae21e4Smrg    if ( ( $item % $last ) == 0 ) {
469a5ae21e4Smrg        my $chars = "THE QUICK BROWN FOX\nJUMPED OVER THE LAZY DOG";
470a5ae21e4Smrg        $text = "";
471a5ae21e4Smrg        for my $n ( 0 .. length($chars) ) {
472a5ae21e4Smrg            my $chr = substr( $chars, $n, 1 );
473a5ae21e4Smrg            if ( $chr eq " " ) {
474a5ae21e4Smrg                $chr = "  ";
475a5ae21e4Smrg            }
476a5ae21e4Smrg            elsif ( ord($chr) < 32 ) {
477a5ae21e4Smrg
478a5ae21e4Smrg                # leave control characters as-is
479a5ae21e4Smrg            }
480a5ae21e4Smrg            else {
481a5ae21e4Smrg                $chr = chr( 0xff00 + ord($chr) - 32 );
482a5ae21e4Smrg            }
483a5ae21e4Smrg            $text .= $chr;
484a5ae21e4Smrg        }
485a5ae21e4Smrg    }
486a5ae21e4Smrg    elsif ( ( $item % $last ) == 1 ) {
487a5ae21e4Smrg        $text = chr(0x442) . chr(0x435) . chr(0x441) . chr(0x442);
488a5ae21e4Smrg    }
489a5ae21e4Smrg    elsif ( ( $item % $last ) == 2 ) {
490a5ae21e4Smrg        for my $chr ( 0x391 .. 0x3a9 ) {
491a5ae21e4Smrg            $text .= chr($chr);
492a5ae21e4Smrg        }
493a5ae21e4Smrg    }
494a5ae21e4Smrg    elsif ( ( $item % $last ) == 3 ) {
495a5ae21e4Smrg        for my $chr ( 0x3b1 .. 0x3c9 ) {
496a5ae21e4Smrg            $text .= chr($chr);
497a5ae21e4Smrg        }
498a5ae21e4Smrg    }
499a5ae21e4Smrg    return $text;
500a5ae21e4Smrg}
501a5ae21e4Smrg
502a5ae21e4Smrgsub set_titletext() {
503a5ae21e4Smrg    my $opt = &get_selector;
504a5ae21e4Smrg    if ( $opt >= 0 ) {
505a5ae21e4Smrg        my $text;
506a5ae21e4Smrg        if ($opt_g) {
507a5ae21e4Smrg
508a5ae21e4Smrg            if (&SendUTF8) {
509a5ae21e4Smrg                $text = &utf8_sample( $utf8_sample++ );
510a5ae21e4Smrg            }
511a5ae21e4Smrg            else {
512a5ae21e4Smrg                # ugly code, but mapping the a/e/i/o/u uppercase accented
513a5ae21e4Smrg                # characters that repeat.
514a5ae21e4Smrg                my $a_chars = chr(192) . chr(193) . chr(194) . chr(196);
515a5ae21e4Smrg                my $e_chars = "";
516a5ae21e4Smrg                my $i_chars = " ";
517a5ae21e4Smrg                my $o_chars = chr(210) . chr(211) . chr(212) . chr(214);
518a5ae21e4Smrg                my $u_chars = "";
519a5ae21e4Smrg                my $gap     = " " . chr(215) . " ";
520a5ae21e4Smrg                for my $chr ( 0 .. 3 ) {
521a5ae21e4Smrg                    $e_chars .= chr( $chr + 200 );
522a5ae21e4Smrg                    $i_chars .= chr( $chr + 204 ) . " ";
523a5ae21e4Smrg                    $u_chars .= chr( $chr + 217 );
524a5ae21e4Smrg                }
525a5ae21e4Smrg                $text =
526a5ae21e4Smrg                    $a_chars
527a5ae21e4Smrg                  . $gap
528a5ae21e4Smrg                  . $e_chars
529a5ae21e4Smrg                  . $gap
530a5ae21e4Smrg                  . $i_chars
531a5ae21e4Smrg                  . $gap
532a5ae21e4Smrg                  . $o_chars
533a5ae21e4Smrg                  . $gap
534a5ae21e4Smrg                  . $u_chars;
535a5ae21e4Smrg            }
5365104ee6eSmrg            &cooked;
537a5ae21e4Smrg            printf "%s\r\n", $text;
5385104ee6eSmrg            &raw;
539a5ae21e4Smrg        }
540a5ae21e4Smrg        else {
5415104ee6eSmrg            $text = &get_line;
542a5ae21e4Smrg        }
543a5ae21e4Smrg        $titlestack[$SP] = $text;
544a5ae21e4Smrg        $item_stack[$SP] = $opt;
545a5ae21e4Smrg        $mode_stack[$SP] = $TM;
546a5ae21e4Smrg        if (&SendHEX) {
547a5ae21e4Smrg            my $octets =
548a5ae21e4Smrg              encode( ( &SendUTF8 ? "utf-8" : "iso-8859-1" ), $text );
549a5ae21e4Smrg            $text = &to_hex($octets);
550a5ae21e4Smrg        }
551a5ae21e4Smrg        &send_command( sprintf( "%s%s;%s%s", $OSC, $opt, $text, $ST ) );
552a5ae21e4Smrg    }
553a5ae21e4Smrg}
554a5ae21e4Smrg
555a5ae21e4Smrgsub save_title() {
556a5ae21e4Smrg    my $opt = &get_selector;
557a5ae21e4Smrg    if ( $opt >= 0 ) {
558a5ae21e4Smrg        &send_command( sprintf( "%s22;%st", $CSI, $opt ) );
559a5ae21e4Smrg        ++$SP;
560a5ae21e4Smrg        $titlestack[$SP] = $titlestack[ $SP - 1 ];
561a5ae21e4Smrg        $item_stack[$SP] = $opt;
562a5ae21e4Smrg        $mode_stack[$SP] = $mode_stack[ $SP - 1 ];
563a5ae21e4Smrg    }
564a5ae21e4Smrg}
565a5ae21e4Smrg
566a5ae21e4Smrgsub restore_title($) {
567a5ae21e4Smrg    my $set = shift;
568a5ae21e4Smrg    my $opt = &get_selector unless ($set);
569a5ae21e4Smrg    if ( $opt >= 0 and $SP > 0 ) {
570a5ae21e4Smrg        $opt = $item_stack[$SP] if ($set);
571a5ae21e4Smrg        &send_command( sprintf( "%s23;%st", $CSI, $opt ) );
572a5ae21e4Smrg        $SP--;
573a5ae21e4Smrg    }
574a5ae21e4Smrg}
575a5ae21e4Smrg
576a5ae21e4Smrgsub get_xprop($$) {
577a5ae21e4Smrg    my $id   = shift;
578a5ae21e4Smrg    my $name = shift;
579a5ae21e4Smrg    my @data = &read_cmd("xprop -id $id");
580a5ae21e4Smrg    my $prop = "";
581a5ae21e4Smrg    for my $n ( 0 .. $#data ) {
582a5ae21e4Smrg        if ( $data[$n] =~ /$name\([^)]+\) =/ ) {
583a5ae21e4Smrg            $prop = $data[$n];
584a5ae21e4Smrg            $prop =~ s/^[^=]*=\s*//;
585a5ae21e4Smrg            $prop =~ s/"//g;
586a5ae21e4Smrg            last;
587a5ae21e4Smrg        }
588a5ae21e4Smrg    }
589a5ae21e4Smrg    return $prop;
590a5ae21e4Smrg}
591a5ae21e4Smrg
592a5ae21e4Smrgsub get_WM_NAME() {
593a5ae21e4Smrg    $wm_name = "missing WM_NAME";
594a5ae21e4Smrg    my $supwin = `xprop -root '_NET_SUPPORTING_WM_CHECK'`;
595a5ae21e4Smrg    if ( $supwin ne "" ) {
596a5ae21e4Smrg        $supwin =~ s/^.*(0x[[:xdigit:]]+).*/$1/;
597a5ae21e4Smrg        $wm_name = &get_xprop( $supwin, "_NET_WM_NAME" );
598a5ae21e4Smrg        $wm_name = "unknown" if ( $wm_name eq "" );
599a5ae21e4Smrg        printf "** using \"$wm_name\" window manager\n";
600a5ae21e4Smrg    }
601a5ae21e4Smrg}
602a5ae21e4Smrg
603a5ae21e4Smrgsub main::HELP_MESSAGE() {
604a5ae21e4Smrg    printf STDERR <<EOF
605a5ae21e4SmrgUsage: $0 [options]
606a5ae21e4SmrgOptions:
607a5ae21e4Smrg  -8      use 8-bit controls
608a5ae21e4Smrg  -b      use BEL rather than ST for terminating strings
6095104ee6eSmrg  -c FILE read commands from this file.
610a5ae21e4Smrg  -g      generate title-strings rather than prompting
6115104ee6eSmrg  -l FILE log commands to this file.
6125104ee6eSmrg  -s SECS sleep this long each time a command is read from file
613a5ae21e4Smrg  -v      verbose
614a5ae21e4SmrgEOF
615a5ae21e4Smrg      ;
616a5ae21e4Smrg    exit 1;
617a5ae21e4Smrg}
618a5ae21e4Smrg
619a5ae21e4Smrg$Getopt::Std::STANDARD_HELP_VERSION = 1;
6205104ee6eSmrg&getopts('bc:gl:s:v8') || &main::HELP_MESSAGE;
6215104ee6eSmrg
6225104ee6eSmrgif ($opt_c) {
6235104ee6eSmrg    open( my $cmd_fp, "<", $opt_c ) || &main::HELP_MESSAGE;
6245104ee6eSmrg    @cmd_buffer = <$cmd_fp>;
6255104ee6eSmrg    close $cmd_fp;
6265104ee6eSmrg    chomp @cmd_buffer;
6275104ee6eSmrg    $cmd_index = 0;
6285104ee6eSmrg}
6295104ee6eSmrg
6305104ee6eSmrgif ($opt_l) {
6315104ee6eSmrg    open( $log_fp, ">", $opt_l ) || &main::HELP_MESSAGE;
6325104ee6eSmrg}
6335104ee6eSmrg
6345104ee6eSmrg$opt_s = "1" unless ( defined($opt_s) and ( $opt_s =~ /^(\d*\.)?\d+$/ ) );
635a5ae21e4Smrg
636a5ae21e4Smrg$ST = "\007" if ($opt_b);
637a5ae21e4Smrg
6385104ee6eSmrg$SP              = 0;
6395104ee6eSmrg$titlestack[$SP] = "unknown";
640a5ae21e4Smrg$item_stack[$SP] = 0;
641a5ae21e4Smrg$mode_stack[$SP] = $TM = 0;
642a5ae21e4Smrg
643a5ae21e4Smrgbinmode( STDOUT, ":utf8" ) if ( $encoding eq "utf-8" );
644a5ae21e4Smrgif ($opt_8) {
645a5ae21e4Smrg    if ( $encoding eq "utf-8" ) {
646a5ae21e4Smrg        undef $opt_8;
647a5ae21e4Smrg        printf "...ignoring -8 option since locale uses %s\n", $encoding;
648a5ae21e4Smrg    }
649a5ae21e4Smrg    else {
650a5ae21e4Smrg        printf STDERR "\x1b G";
651a5ae21e4Smrg        $CSI = "\x9b";
6525104ee6eSmrg        $DCS = "\x90";
653a5ae21e4Smrg        $OSC = "\x9d";
654a5ae21e4Smrg        $ST  = "\x9c";
655a5ae21e4Smrg    }
656a5ae21e4Smrg}
657a5ae21e4Smrg
658a5ae21e4Smrg&get_WM_NAME;
659a5ae21e4Smrg
660a5ae21e4Smrg&raw;
6615104ee6eSmrg&get_titlemodes;
6625104ee6eSmrg&get_level;
663a5ae21e4Smrg&raw;
664a5ae21e4Smrgwhile (1) {
665a5ae21e4Smrg    my $cmd;
666a5ae21e4Smrg
6675104ee6eSmrg    printf "\r\n[$SP:$SQ] Command (? for help):";
6685104ee6eSmrg    $cmd = &get_char;
669a5ae21e4Smrg    if ( not $cmd ) {
670a5ae21e4Smrg        sleep 1;
671a5ae21e4Smrg    }
672a5ae21e4Smrg    elsif ( $cmd eq "?" ) {
673a5ae21e4Smrg        printf "\r\n? help,"
674a5ae21e4Smrg          . " d=display,"
675a5ae21e4Smrg          . " m/M=set/reset mode,"
676a5ae21e4Smrg          . " p=set title,"
677a5ae21e4Smrg          . " q=quit,"
678a5ae21e4Smrg          . " r=restore,"
679a5ae21e4Smrg          . " s=save\r\n";
680a5ae21e4Smrg    }
681a5ae21e4Smrg    elsif ( $cmd eq "#" ) {
682a5ae21e4Smrg        printf " ...comment\r\n\t#";
6835104ee6eSmrg        &get_line;
684a5ae21e4Smrg    }
685a5ae21e4Smrg    elsif ( $cmd eq "!" ) {
686a5ae21e4Smrg        printf " ...shell\r\n";
687a5ae21e4Smrg        &cooked;
688a5ae21e4Smrg        system( $ENV{SHELL} );
689a5ae21e4Smrg        &raw;
690a5ae21e4Smrg    }
691a5ae21e4Smrg    elsif ( $cmd eq "d" ) {
692a5ae21e4Smrg        printf " ...display\r\n";
693a5ae21e4Smrg        &display_info;
694a5ae21e4Smrg    }
695a5ae21e4Smrg    elsif ( $cmd eq "p" ) {
696a5ae21e4Smrg        printf " ...set text\r\n";
697a5ae21e4Smrg        &set_titletext;
698a5ae21e4Smrg    }
699a5ae21e4Smrg    elsif ( $cmd eq "q" ) {
700a5ae21e4Smrg        printf " ...quit\r\n";
701a5ae21e4Smrg        last;
702a5ae21e4Smrg    }
703a5ae21e4Smrg    elsif ( $cmd eq "s" ) {
704a5ae21e4Smrg        printf " ...save title\r\n";
705a5ae21e4Smrg        &save_title;
706a5ae21e4Smrg    }
707a5ae21e4Smrg    elsif ( $cmd eq "r" ) {
708a5ae21e4Smrg        printf " ...restore title\r\n";
709a5ae21e4Smrg        &restore_title(0);
710a5ae21e4Smrg    }
711a5ae21e4Smrg    elsif ( $cmd eq "m" ) {
712a5ae21e4Smrg        printf " ...set title mode\r\n";
713a5ae21e4Smrg        &set_titlemode(1);
714a5ae21e4Smrg    }
715a5ae21e4Smrg    elsif ( $cmd eq "M" ) {
716a5ae21e4Smrg        printf " ...reset title mode\r\n";
717a5ae21e4Smrg        &set_titlemode(0);
718a5ae21e4Smrg    }
719a5ae21e4Smrg}
720a5ae21e4Smrg
721a5ae21e4Smrg# when unstacking here, just use the selector used for the push
722a5ae21e4Smrgwhile ( $SP > 0 ) {
723a5ae21e4Smrg    &restore_title(1);
724a5ae21e4Smrg}
725a5ae21e4Smrg
726a5ae21e4Smrg&send_command( sprintf( "%s>T", $CSI ) );    # reset title-modes to default
727a5ae21e4Smrg
728a5ae21e4Smrg&cooked;
729a5ae21e4Smrg
730a5ae21e4Smrgprintf "\x1b F" if ($opt_8);
731