File Coverage

blib/lib/X11/Protocol/WM.pm
Criterion Covered Total %
statement 20 352 5.6
branch 1 134 0.7
condition 2 91 2.2
subroutine 8 62 12.9
pod 32 34 94.1
total 63 673 9.3


line stmt bran cond sub pod time code
1             # Copyright 2011, 2012, 2013, 2014, 2016 Kevin Ryde
2              
3             # This file is part of X11-Protocol-Other.
4             #
5             # X11-Protocol-Other is free software; you can redistribute it and/or modify
6             # it under the terms of the GNU General Public License as published by the
7             # Free Software Foundation; either version 3, or (at your option) any later
8             # version.
9             #
10             # X11-Protocol-Other is distributed in the hope that it will be useful, but
11             # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12             # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13             # for more details.
14             #
15             # You should have received a copy of the GNU General Public License along
16             # with X11-Protocol-Other. If not, see .
17              
18              
19             # /usr/share/doc/xorg-docs/icccm/icccm.txt.gz
20             # /usr/share/doc/xorg-docs/ctext/ctext.txt.gz
21             #
22             # /usr/include/X11/Xutil.h
23             # Xlib structs.
24             #
25             # http://www.pps.univ-paris-diderot.fr/%7Ejch/software/UTF8_STRING/
26             # http://www.pps.univ-paris-diderot.fr/%7Ejch/software/UTF8_STRING/UTF8_STRING.text
27             # /so/netwm/UTF8_STRING.text
28              
29 4     4   11021 BEGIN { require 5 }
30             package X11::Protocol::WM;
31 4     4   14 use strict;
  4         3  
  4         61  
32 4     4   12 use Carp;
  4         4  
  4         178  
33 4     4   1562 use X11::AtomConstants;
  4         4  
  4         125  
34              
35 4     4   15 use vars '$VERSION', '@ISA', '@EXPORT_OK';
  4         4  
  4         163  
36             $VERSION = 30;
37              
38 4     4   10 use Exporter;
  4         3  
  4         269  
39             @ISA = ('Exporter');
40             @EXPORT_OK = qw(
41             frame_window_to_client
42             root_to_virtual_root
43              
44             change_wm_hints
45             change_net_wm_state
46              
47             get_wm_icon_size
48             get_wm_hints
49             get_wm_state
50             get_net_frame_extents
51             get_net_wm_state
52             set_text_property
53              
54             set_wm_class
55             set_wm_client_machine
56             set_wm_client_machine_from_syshostname
57             set_wm_command
58             set_wm_hints
59             set_wm_name
60             set_wm_normal_hints
61             set_wm_icon_name
62             set_wm_protocols
63             set_wm_transient_for
64              
65             set_motif_wm_hints
66              
67             set_net_wm_pid
68             set_net_wm_state
69             set_net_wm_user_time
70             set_net_wm_window_type
71              
72             pack_wm_hints
73             pack_wm_size_hints
74             pack_motif_wm_hints
75             unpack_wm_hints
76             unpack_wm_state
77             aspect_to_num_den
78              
79             iconify
80             withdraw
81             );
82              
83             # uncomment this to run the ### lines
84             # use Smart::Comments;
85              
86              
87             #------------------------------------------------------------------------------
88             # shared bits
89              
90             BEGIN {
91 4 50 33 4   379 eval 'utf8->can("is_utf8") && *is_utf8 = \&utf8::is_utf8' # 5.8.1
  4   33 4   1798  
  4         26776  
  4         164  
92             || eval 'use Encode "is_utf8"; 1' # 5.8.0
93             || eval 'sub is_utf8 { 0 }; 1' # 5.6 fallback
94             || die 'Oops, cannot create is_utf8() subr: ',$@;
95             }
96             ### \&is_utf8
97              
98             sub set_text_property {
99 0     0 1   my ($X, $window, $prop, $str) = @_;
100 0 0         if (defined $str) {
101 0           my $type;
102 0           ($type, $str) = _to_TEXT ($X, $str);
103 0           $X->ChangeProperty ($window,
104             $prop, # prop name
105             $type, # type
106             8, # format
107             'Replace',
108             $str);
109             } else {
110 0           $X->DeleteProperty ($window, $prop);
111             }
112             }
113              
114             # Maybe ...
115             #
116             # =item C<$str = _to_STRING ($str)>
117             #
118             # Convert C<$str> to latin-1 bytes for use in a STRING property. If C<$str>
119             # is already bytes then they're presumed to be latin-1. If C<$str> is Perl
120             # 5.8 wide chars then it's converted with the Encode module, and C
121             # if cannot be represented as a STRING.
122             #
123             sub _to_STRING {
124 0     0     my ($str) = @_;
125 0 0         if (is_utf8($str)) {
126 0           require Encode;
127             # croak in the interests of not letting bad values go through unnoticed,
128             # nor letting a mangled name be stored
129 0           return Encode::encode ('iso-8859-1', $str, Encode::FB_CROAK());
130             } else {
131 0           return $str;
132             }
133             }
134              
135             # Maybe ...
136             #
137             # =item C<($atom, $bytes) = _to_TEXT ($X, $str)>
138             #
139             # Convert C<$str> to either C or C per L
140             # Properties> above. The returned C<$atom> (an integer) is the either
141             # C or C and C<$bytes> are bytes of that type.
142             #
143             sub _to_TEXT {
144 0     0     my ($X, $str) = @_;
145 0 0         if (! is_utf8($str)) {
146             # bytes or pre-5.8 taken to be latin-1
147 0           return (X11::AtomConstants::STRING(), $str);
148             }
149 0           require Encode;
150             {
151 0           my $input = $str; # don't clobber $str
  0            
152 0           my $bytes = Encode::encode ('iso-8859-1', $input, Encode::FB_QUIET());
153 0 0         if (length($input) == 0) {
154             # latin-1 suffices
155 0           return (X11::AtomConstants::STRING(), $bytes);
156             }
157             }
158 0           require Encode::X11;
159 0           return ($X->atom('COMPOUND_TEXT'),
160             Encode::encode ('x11-compound-text', $str, Encode::FB_WARN()));
161             }
162              
163             # Set a property on $window (integer XID) to a single CARD32 integer value.
164             # $prop is the property (integer atom ID).
165             # $type is the property type (integer atom ID).
166             # $value is a 32-bit integer to store, or undef to delete the property.
167             #
168             # The ICCCM or similar specification will say what C<$type> should be in a
169             # property. Often there's only one type, but in any case C<$type> indicates
170             # what has been stored. This might be for example the atom for "PIXMAP" if
171             # $value is a pixmap XID. Things which are counts or numbers are usually
172             # the atom "CARDINAL".
173             #
174             sub _set_card32_property {
175 0     0     my ($X, $window, $prop, $type, $value) = @_;
176 0 0         if (defined $value) {
177 0           $X->ChangeProperty ($window,
178             $prop, # prop name
179             $type, # type
180             32, # format
181             'Replace',
182             pack ('L', $value));
183             } else {
184 0           $X->DeleteProperty ($window, $prop);
185             }
186             }
187              
188             # or maybe $X->num('IDorNone',$xid)
189             # $X->num('XID',$xid)
190             sub _num_none {
191 0     0     my ($xid) = @_;
192 0 0 0       if (defined $xid && $xid eq "None") {
193 0           return 0;
194             } else {
195 0           return $xid;
196             }
197             }
198              
199             # or maybe $X->interp('IDorNone',$xid) or 'XIDorNone'
200             sub _none_interp {
201 0     0     my ($X, $xid) = @_;
202 0 0 0       if ($X->{'do_interp'} && $xid == 0) {
203 0           return 'None';
204             } else {
205 0           return $xid;
206             }
207             }
208              
209             # return $root or if that's undef then lookup root of $window
210             sub _root_for_window {
211 0     0     my ($X, $window, $root) = @_;
212 0 0         if (! defined $root) {
213 0           ($root) = $X->QueryTree($window);
214             }
215 0           return $root;
216             }
217              
218             #------------------------------------------------------------------------------
219             # frame_window_to_client()
220              
221             # /usr/share/doc/libxmu-headers/Xmu.txt.gz for XmuClientWindow()
222             # https://bugs.freedesktop.org/show_bug.cgi?id=7474
223             # XmuClientWindow() bottom-up was hurting fluxbox and probably ion, pekwm
224             #
225             sub frame_window_to_client {
226 0     0 1   my ($X, $frame) = @_;
227              
228 0           my @search = ($frame);
229 0           my $property = $X->atom('WM_STATE');
230              
231             # ENHANCE-ME: do three reqs in parallel, better yet all reqs for an
232             # @search depth level in parallel
233              
234 0           my $count = 0;
235 0           OUTER: foreach (1 .. 5) { # limit search depth for safety
236 0           my $child;
237 0           foreach $child (splice @search) { # breadth-first search
238             ### look at: sprintf '0x%X', $child
239              
240 0 0         if ($count++ > 50) {
241             ### abandon search at count: $count
242 0           return undef;
243             }
244              
245             {
246 0           my $ret = $X->robust_req ('GetWindowAttributes', $child);
247 0 0         if (! ref $ret) {
248             ### some error, skip this child
249 0           next;
250             }
251 0           my %attr = @$ret;
252             ### map_state: $attr{'map_state'}
253 0 0         if ($attr{'map_state'} ne 'Viewable') {
254             ### not viewable, skip
255 0           next;
256             }
257             }
258             {
259 0           my $ret = $X->robust_req ('GetProperty',
  0            
260             $child, $property, 'AnyPropertyType',
261             0, # offset
262             0, # length
263             0); # delete;
264 0 0         if (! ref $ret) {
265             ### some error, skip this child
266 0           next;
267             }
268 0           my ($value, $type, $format, $bytes_after) = @$ret;
269 0 0         if ($type) {
270             ### found
271 0           return $child;
272             }
273             }
274             {
275 0           my $ret = $X->robust_req ('QueryTree', $child);
  0            
  0            
276 0 0         if (ref $ret) {
277 0           my ($root, $parent, @children) = @$ret;
278             ### push children: @children
279             # @children are in bottom up order, prefer the topmost
280 0           push @search, reverse @children;
281             }
282             }
283             }
284             }
285             ### not found
286 0           return undef;
287             }
288              
289              
290             #------------------------------------------------------------------------------
291             # root_to_virtual_root()
292              
293             # ENHANCE-ME: Could do all the GetProperty checks in parallel.
294             # Could intern the VROOT atom during the QueryTree too.
295             #
296             sub root_to_virtual_root {
297 0     0 1   my ($X, $root) = @_;
298             ### root_to_virtual_root(): $root
299              
300 0           my ($root_root, $root_parent, @toplevels) = $X->QueryTree($root);
301 0           my $toplevel;
302 0           foreach $toplevel (@toplevels) {
303             ### $toplevel
304 0           my @ret = $X->robust_req ('GetProperty',
305             $toplevel,
306             $X->atom('__SWM_VROOT'),
307             X11::AtomConstants::WINDOW(), # type
308             0, # offset
309             1, # length x 32bits
310             0); # delete;
311             ### @ret
312 0 0         next unless ref $ret[0]; # ignore errors from toplevels destroyed etc
313              
314 0           my ($value, $type, $format, $bytes_after) = @{$ret[0]};
  0            
315 0 0         if (my $vroot = unpack 'L', $value) {
316             ### found: $vroot
317 0           return $vroot;
318             }
319             }
320 0           return $root;
321             }
322              
323              
324             #------------------------------------------------------------------------------
325             # WM_CLASS
326              
327             sub set_wm_class {
328 0     0 1   my ($X, $window, $instance, $class) = @_;
329 0 0         if (defined $instance) {
330 0           my $str = _to_STRING($instance)."\0"._to_STRING($class)."\0";
331 0           $X->ChangeProperty($window,
332             X11::AtomConstants::WM_CLASS(), # prop
333             X11::AtomConstants::STRING(), # type
334             8, # byte format
335             'Replace',
336             $str);
337             } else {
338 0           $X->DeleteProperty ($window, X11::AtomConstants::WM_CLASS());
339             }
340             }
341              
342              
343             #------------------------------------------------------------------------------
344             # WM_CLIENT_MACHINE
345              
346             sub set_wm_client_machine {
347 0     0 1   my ($X, $window, $hostname) = @_;
348 0           set_text_property ($X, $window,
349             X11::AtomConstants::WM_CLIENT_MACHINE(), $hostname);
350             }
351              
352             sub set_wm_client_machine_from_syshostname {
353 0     0 1   my ($X, $window) = @_;
354 0           require Sys::Hostname;
355 0           set_wm_client_machine ($X, $window, eval { Sys::Hostname::hostname() });
  0            
356             }
357              
358              
359             #------------------------------------------------------------------------------
360             # WM_COMMAND
361              
362             sub set_wm_command {
363 0     0 1   my $X = shift;
364 0           my $window = shift;
365              
366 0 0 0       if (@_ && ! defined $_[0]) {
367             # this not documented ...
368 0           $X->DeleteProperty ($window, X11::AtomConstants::WM_COMMAND());
369 0           return;
370             }
371              
372             # cf join() gives a wide-char result if any parts wide, upgrading byte
373             # strings as if they were latin-1
374 0           my $value = '';
375 0           my $type = X11::AtomConstants::STRING();
376 0           my $str;
377 0           foreach $str (@_) {
378 0           my ($atom, $bytes) = _to_TEXT($X,$str);
379 0 0         if ($atom != X11::AtomConstants::STRING()) {
380 0           $type = $atom; # COMPOUND_TEXT if any part needs COMPOUND_TEXT
381             }
382 0           $value .= "$bytes\0";
383             }
384 0 0         if ($value eq "\0") {
385 0           $value = ""; # this not documented ...
386             # C<$command> can be an empty string "" to mean no known command as a
387             # reply to C ... maybe
388             }
389 0           $X->ChangeProperty ($window,
390             X11::AtomConstants::WM_COMMAND(), # prop name
391             $type, # type
392             8, # format
393             'Replace',
394             $value);
395             }
396              
397              
398             #------------------------------------------------------------------------------
399             # WM_ICON_SIZE
400              
401             sub get_wm_icon_size {
402 0     0 1   my ($X, $root) = @_;
403 0 0         if (! defined $root) {
404 0           $root = $X->root;
405             }
406 0           my ($value, $type, $format, $bytes_after)
407             = $X->GetProperty ($root,
408             X11::AtomConstants::WM_ICON_SIZE(), # property
409             X11::AtomConstants::WM_ICON_SIZE(), # type
410             0, # offset
411             6, # length CARD32s
412             0); # delete;
413 0 0         if ($format == 32) {
414 0           return unpack 'L6', $value;
415             } else {
416 0           return;
417             }
418             }
419              
420              
421             #------------------------------------------------------------------------------
422             # WM_HINTS
423              
424             sub set_wm_hints {
425 0     0 1   my $X = shift;
426 0           my $window = shift;
427             ### set_wm_hints(): @_
428             ### set cards: map {sprintf '%#x',$_} unpack 'L*', pack_wm_hints($X,@_)
429 0           $X->ChangeProperty($window,
430             X11::AtomConstants::WM_HINTS(), # prop name
431             X11::AtomConstants::WM_HINTS(), # type
432             32, # format
433             'Replace',
434             pack_wm_hints($X, @_));
435             }
436              
437             sub get_wm_hints {
438 0     0 1   my ($X, $window) = @_;
439 0           my ($value, $type, $format, $bytes_after)
440             = $X->GetProperty ($window,
441             X11::AtomConstants::WM_HINTS(), # prop name
442             X11::AtomConstants::WM_HINTS(), # type
443             0, # offset
444             9, # length($format), of CARD32
445             0); # no delete
446 0 0         if ($format == 32) {
447             ### got cards: map {sprintf '%#x',$_} unpack 'L*', $value
448 0           return unpack_wm_hints ($X, $value);
449             } else {
450 0           return;
451             }
452             }
453              
454             sub change_wm_hints {
455 0     0 1   my $X = shift;
456 0           my $window = shift;
457 0           set_wm_hints ($X, $window, get_wm_hints($X,$window), @_);
458             }
459              
460             {
461             my $format = 'LLLLLllLL';
462             # The C hint was called "visible" in X11R5. The name "urgency"
463             # is used here per X11R6. The actual field sent and received is the same.
464             #
465             my %key_to_flag = (input => 1,
466             initial_state => 2,
467             icon_pixmap => 4,
468             icon_window => 8,
469             icon_x => 16,
470             icon_y => 16,
471             icon_mask => 32,
472             window_group => 64,
473             # message => 128, # in the code, obsolete
474             # urgency => 256, # in the code
475             );
476              
477             sub pack_wm_hints {
478 0     0 1   my ($X, %hint) = @_;
479             ### pack_wm_hints(): %hint
480 0           my $flags = 0;
481 0 0         if (delete $hint{'message'}) {
482 0           $flags = 128;
483             }
484 0 0         if (delete $hint{'urgency'}) {
485 0           $flags |= 256;
486             }
487 0           my $key;
488 0           foreach $key (keys %hint) {
489 0   0       my $flag_bit = $key_to_flag{$key}
490             || croak "Unknown WM_HINT field: ",$key;
491 0 0         if (defined $hint{$key}) {
492 0           $flags |= $flag_bit;
493             }
494             }
495             return pack ($format,
496             $flags,
497             $hint{'input'} || 0, # CARD32 bool
498             _wmstate_num($hint{'initial_state'}) || 0, # CARD32 enum
499             _num_none($hint{'icon_pixmap'}) || 0, # PIXMAP
500             _num_none($hint{'icon_window'}) || 0, # WINDOW
501             $hint{'icon_x'} || 0, # INT32
502             $hint{'icon_y'} || 0, # INT32
503             _num_none($hint{'icon_mask'}) || 0, # PIXMAP
504 0   0       _num_none($hint{'window_group'}) || 0); # WINDOW
      0        
      0        
      0        
      0        
      0        
      0        
      0        
505             }
506              
507             # X11R2 Xlib had a bug where XSetWMHints() set a WM_HINTS property to only
508             # 8 CARD32s, chopping off the window_group field. This was due to
509             # Xatomtype.h NumPropWMHintsElements being 8 instead of 9. If the length
510             # of $bytes here is only 8 then ignore any window_group bit in the flags
511             # and don't return a window_group field. X11R2 source available at
512             # http://ftp.x.org/pub/X11R2/X.V11R2.tar.gz
513             #
514             my @keys = ('input',
515             'initial_state',
516             'icon_pixmap',
517             'icon_window',
518             'icon_x',
519             'icon_y',
520             'icon_mask',
521             'window_group',
522             # 'message', # in the code, and obsolete ...
523             # 'urgency', # in the code
524             );
525             my @interp = (\&_unchanged, # input
526             \&_wmstate_interp, # initial_state
527             \&_none_interp, # icon_pixmap
528             \&_none_interp, # icon_window
529             \&_unchanged, # icon_x
530             \&_unchanged, # icon_y
531             \&_none_interp, # icon_mask
532             \&_none_interp, # window_group
533             );
534             sub unpack_wm_hints {
535 0     0 1   my ($X, $bytes) = @_;
536             ### unpack_wm_hints(): unpack 'L*', $bytes
537 0           my ($flags, @values) = unpack ($format, $bytes);
538 0           my $bit = 1;
539 0           my @ret;
540             my $i;
541 0           foreach $i (0 .. $#keys) {
542 0           my $value = $values[$i];
543 0 0         if (! defined $value) {
544             # if $bytes is only 8 CARD32s as from X11R2 then omit window_group
545             # from the return
546 0           next;
547             }
548 0 0         if ($flags & $bit) {
549 0           push @ret, $keys[$i], &{$interp[$i]}($X, $value);
  0            
550             }
551 0           $bit <<= ($i!=4); # icon_x,icon_y both at $bit==16
552             }
553 0 0         if ($flags & 128) {
554 0           push @ret, message => 1;
555             }
556 0 0         if ($flags & 256) {
557 0           push @ret, urgency => 1;
558             }
559 0           return @ret;
560             }
561             }
562              
563             sub _unchanged {
564 0     0     my ($X, $value) = @_;
565 0           return $value;
566             }
567              
568              
569             #------------------------------------------------------------------------------
570             # WM_ICON_NAME
571              
572             sub set_wm_icon_name {
573 0     0 1   my ($X, $window, $name) = @_;
574 0           set_text_property ($X, $window, X11::AtomConstants::WM_ICON_NAME(), $name);
575             }
576              
577              
578             #------------------------------------------------------------------------------
579             # WM_NAME
580              
581             sub set_wm_name {
582 0     0 1   my ($X, $window, $name) = @_;
583 0           set_text_property ($X, $window, X11::AtomConstants::WM_NAME(), $name);
584             }
585              
586             #------------------------------------------------------------------------------
587             # WM_PROTOCOLS
588              
589             sub set_wm_protocols {
590 0     0 1   my $X = shift;
591 0           my $window = shift;
592              
593             # ENHANCE-ME: intern all atoms in one round-trip
594 0           my $prop = $X->atom('WM_PROTOCOLS');
595 0 0         if (@_) {
596 0           $X->ChangeProperty($window,
597             $prop, # property
598             X11::AtomConstants::ATOM(), # type
599             32, # format
600             'Replace',
601             pack('L*',_to_atom_nums($X,@_)));
602             } else {
603 0           $X->DeleteProperty ($window, $prop);
604             }
605             }
606             sub _to_atom_nums {
607 0     0     my $X = shift;
608 0 0         return map { ($_ =~ /^\d+$/ ? $_ : $X->atom($_)) } @_;
  0            
609             }
610              
611              
612             #------------------------------------------------------------------------------
613             # WM_STATE enum
614             # For internal use yet ...
615              
616             {
617             my %wmstate = (WithdrawnState => 0,
618             DontCareState => 0, # no longer in ICCCM
619             NormalState => 1,
620             ZoomState => 2, # no longer in ICCCM
621             IconicState => 3,
622             InactiveState => 4, # no longer in ICCCM
623             );
624             sub _wmstate_num {
625 0     0     my ($wmstate) = @_;
626 0 0 0       if (defined $wmstate && defined (my $num = $wmstate{$wmstate})) {
627 0           return $num;
628             }
629 0           return $wmstate;
630             }
631             }
632              
633             {
634             # DontCareState==0 no longer ICCCM
635             my @wmstate = ('WithdrawnState', # 0
636             'NormalState', # 1
637             'ZoomState', # 2, no longer ICCCM
638             'IconicState', # 3
639             'InactiveState', # 4, no longer in ICCCM
640             );
641             sub _wmstate_interp {
642 0     0     my ($X, $num) = @_;
643 0 0 0       if ($X->{'do_interp'} && defined (my $str = $wmstate[$num])) {
644 0           return $str;
645             }
646 0           return $num;
647             }
648             }
649              
650             # Maybe through $X->interp() with ...
651             #
652             # {
653             # # $X->interp('WmState',$num);
654             # # $X->num('WmState',$str);
655             # my %const_arrays
656             # = (
657             # WmState => ['WithdrawnState', # 0
658             # 'NormalState', # 1
659             # 'ZoomState', # 2, no longer ICCCM
660             # 'IconicState', # 3
661             # 'InactiveState', # 4, no longer in ICCCM
662             # ],
663             # );
664             #
665             # my %const_hashes
666             # = (map { $_ => { X11::Protocol::make_num_hash($const_arrays{$_}) } }
667             # keys %const_arrays);
668             #
669             #
670             # sub ext_const_init {
671             # my ($X) = @_;
672             # unless ($X->{'ext_const'}->{'WmState'}) {
673             # %{$X->{'ext_const'}} = (%{$X->{'ext_const'}}, %const_arrays);
674             # $X->{'ext_const_num'} ||= {};
675             # %{$X->{'ext_const_num'}} = (%{$X->{'ext_const_num'}}, %const_hashes);
676             # }
677             # }
678             # }
679              
680              
681             #------------------------------------------------------------------------------
682             # WM_STATE
683              
684             sub get_wm_state {
685 0     0 1   my ($X, $window) = @_;
686 0           my $xa_wm_state = $X->atom('WM_STATE');
687 0           my ($value, $type, $format, $bytes_after)
688             = $X->GetProperty ($window,
689             $xa_wm_state, # property
690             $xa_wm_state, # type
691             0, # offset
692             2, # length, 2 x CARD32
693             0); # delete
694 0 0         if ($format == 32) {
695 0           return unpack_wm_state($X,$value);
696             } else {
697 0           return;
698             }
699             }
700              
701             sub unpack_wm_state {
702 0     0 1   my ($X, $data) = @_;
703 0           my ($state, $icon_window) = unpack 'LL', $data;
704 0           return (_wmstate_interp($X,$state), _none_interp($X,$icon_window));
705             }
706              
707              
708             #------------------------------------------------------------------------------
709             # WM_STATE transitions
710              
711             # cf /so/xorg/libX11-1.4.0/src/Iconify.c
712             #
713             sub iconify {
714 0     0 1   my ($X, $window, $root) = @_;
715             ### iconify(): $window
716              
717             # The icccm spec doesn't seem to say any particular event mask for this
718             # ClientMessage, but follow Xlib Iconify.c and send
719             # SubstructureRedirect+SubstructureNotify.
720             #
721 0           _send_event_to_wm ($X, _root_for_window($X,$window,$root),
722             name => 'ClientMessage',
723             window => $window,
724             type => $X->atom('WM_CHANGE_STATE'),
725             format => 32,
726             data => pack('L5', 3)); # 3=IconicState
727             }
728              
729             # cf /so/xorg/libX11-1.4.0/src/Withdraw.c
730             #
731             sub withdraw {
732 0     0 1   my ($X, $window, $root) = @_;
733             ### withdraw(): $window, $root
734 0           $root = _root_for_window($X,$window,$root); # QueryTree before unmap
735 0           $X->UnmapWindow ($window);
736 0           _send_event_to_wm ($X, $root,
737             name => 'UnmapNotify',
738             event => $root,
739             window => $window,
740             from_configure => 0);
741             }
742              
743             # =item C<_send_event_to_wm ($X, $root, name=E$str,...)>
744             #
745             # Send an event to the window manager by C<$X-ESendEvent()> to the given
746             # C<$root> (integer XID of a root window).
747             #
748             # The key/value parameters specify an event packet as per
749             # C<$X-Epack_event()>. Often this is a C event, but any
750             # type can be sent. (For example C sends a synthetic
751             # C.)
752             #
753             # But: event-mask=ColormapChange for own colormap install setups ...
754             # But: event-mask=StructureNotify for "manager" acquiring resource ...
755             #
756             sub _send_event_to_wm {
757 0     0     my $X = shift;
758 0           my $root = shift;
759 0           $X->SendEvent ($root,
760             0, # all clients
761             $X->pack_event_mask('SubstructureRedirect',
762             'SubstructureNotify'),
763             $X->pack_event(@_));
764             }
765              
766              
767             #------------------------------------------------------------------------------
768             # WM_TRANSIENT
769              
770             # $transient_for eq 'None' supported for generality, but not yet documented
771             # since not sure such a property value would be ICCCM compliant
772             #
773             sub set_wm_transient_for {
774 0     0 1   my ($X, $window, $transient_for) = @_;
775 0           _set_card32_property ($X, $window,
776             X11::AtomConstants::WM_TRANSIENT_FOR(), # prop name
777             X11::AtomConstants::WINDOW(), # type
778             _num_none ($transient_for));
779             }
780              
781             # not sure about this, might be only used by window manager, not a client
782             # =item C<$transient_for = X11::Protocol::WM::get_wm_transient_for ($X, $window)>
783             # sub get_wm_transient_for {
784             # my ($X, $window) = @_;
785             # _get_card32_property ($X, $window,
786             # X11::AtomConstants::WM_TRANSIENT_FOR(),
787             # X11::AtomConstants::WINDOW());
788             # }
789              
790              
791             #------------------------------------------------------------------------------
792             # _MOTIF_WM_HINTS
793              
794             sub set_motif_wm_hints {
795 0     0 1   my $X = shift;
796 0           my $window = shift;
797 0           $X->ChangeProperty($window,
798             $X->atom('_MOTIF_WM_HINTS'), # property
799             $X->atom('_MOTIF_WM_HINTS'), # type
800             32, # format
801             'Replace',
802             pack_motif_wm_hints ($X, @_));
803             }
804              
805             {
806             # per /usr/include/Xm/MwmUtil.h
807             my %key_to_flag = (functions => 1,
808             decorations => 2,
809             input_mode => 4,
810             status => 8,
811             );
812             sub pack_motif_wm_hints {
813 0     0 0   my ($X, %hint) = @_;
814              
815 0           my $flags = 0;
816 0           my $key;
817 0           foreach $key (keys %hint) {
818 0 0         if (defined $hint{$key}) {
819 0           $flags |= $key_to_flag{$key};
820             } else {
821 0           croak "Unrecognised _MOTIF_WM_HINTS field: ",$key;
822             }
823             }
824             pack ('L5',
825             $flags,
826             $hint{'functions'} || 0,
827             $hint{'decorations'} || 0,
828             _motif_input_mode_num($X, $hint{'input_mode'} || 0),
829 0   0       $hint{'status'} || 0);
      0        
      0        
      0        
830             }
831             }
832             {
833             my %input_mode_num = (modeless => 0,
834             primary_application_modal => 1,
835             system_modal => 2,
836             full_application_modal => 3,
837              
838             # application_modal => 1,
839             );
840             sub _motif_input_mode_num {
841 0     0     my ($X, $input_mode) = @_;
842 0 0         if (exists $input_mode_num{$input_mode}) {
843 0           return $input_mode_num{$input_mode};
844             } else {
845 0           return $input_mode;
846             }
847             }
848             }
849              
850              
851              
852             #------------------------------------------------------------------------------
853             # _NET_FRAME_EXTENTS
854              
855             sub get_net_frame_extents {
856 0     0 1   my ($X, $window) = @_;
857 0           my ($value, $type, $format, $bytes_after)
858             = $X->GetProperty ($window,
859             $X->atom('_NET_FRAME_EXTENTS'), # property
860             X11::AtomConstants::CARDINAL(), # type
861             0, # offset
862             4, # length, 4 x CARD32
863             0); # delete
864 0 0         if ($format == 32) {
865 0           return unpack 'L4', $value;
866             } else {
867 0           return;
868             }
869             }
870              
871             #------------------------------------------------------------------------------
872             # _NET_WM_PID
873              
874             sub set_net_wm_pid {
875 0     0 1   my ($X, $window, $pid) = @_;
876 0 0         if (@_ < 3) { $pid = $$; }
  0            
877 0           _set_card32_property ($X,
878             $window,
879             $X->atom('_NET_WM_PID'),
880             X11::AtomConstants::CARDINAL(),
881             $pid);
882             }
883              
884             #------------------------------------------------------------------------------
885             # _NET_WM_STATE
886              
887             sub get_net_wm_state {
888 0     0 1   my ($X, $window) = @_;
889             # ENHANCE-ME: maybe atom_names() for parallel name fetch
890 0           return map {_net_wm_state_interp($X,$_)} get_net_wm_state_atoms($X,$window);
  0            
891             }
892             # $atom is an atom integer, return a string like "FULLSCREEN".
893             sub _net_wm_state_interp {
894 0     0     my ($X, $atom) = @_;
895 0           my $state = $X->atom_name($atom);
896 0           $state =~ s/^_NET_WM_STATE_//;
897 0           return $state;
898             }
899             sub get_net_wm_state_atoms {
900 0     0 1   my ($X, $window) = @_;
901 0           my ($value, $type, $format, $bytes_after)
902             = $X->GetProperty ($window,
903             $X->atom('_NET_WM_STATE'), # property
904             X11::AtomConstants::ATOM(), # type
905             0, # offset
906             999, # length limit
907             0); # delete
908 0 0         if ($format == 32) {
909 0           return unpack('L*', $value);
910             } else {
911 0           return;
912             }
913             }
914              
915             # $state is a string "_NET_WM_STATE_FULLSCREEN" etc, or an integer atom
916             # number. Return an integer atom number.
917             sub _net_wm_state_num {
918 0     0     my ($X, $state) = @_;
919 0 0         if (! defined $state) {
920 0           return 0;
921             }
922 0 0         if ($state =~ /^\d+$/) {
923 0           return $state; # a number already
924             }
925 0 0         if ($state !~ /^_NET_WM_STATE_/) {
926 0           $state = '_NET_WM_STATE_' . $state;
927             }
928 0           return $X->atom($state);
929             }
930              
931             sub set_net_wm_state {
932 0     0 1   my $X = shift;
933 0           my $window = shift;
934             $X->ChangeProperty($window,
935             $X->atom('_NET_WM_STATE'), # property
936             X11::AtomConstants::ATOM(), # type
937             32, # format
938             'Replace',
939 0           pack('L*', map {_net_wm_state_num($X,$_)} @_));
  0            
940             }
941              
942             {
943             my %_net_wm_state_action_num = (remove => 0,
944             add => 1,
945             toggle => 2);
946             # $action is a string "add" etc, or a number 0,1,2.
947             # Return a number 0,1,2.
948             sub _net_wm_state_action_num {
949 0     0     my ($X, $action) = @_;
950             ### _net_wm_state_action_num(): $action
951 0 0         if ($action =~ /^\d+$/) {
952 0           return $action; # a number already
953             }
954 0           my $num = $_net_wm_state_action_num{$action};
955 0 0         if (defined $num) {
956 0           return $num;
957             }
958 0           croak 'Unrecognized _NET_WM_STATE action: ',$action;
959             }
960             }
961              
962             {
963             my %_net_wm_source_num = (none => 0,
964             normal => 1,
965             user => 2);
966             # $source is a string "normal" etc, or a number 0,1,2.
967             # Return a number 0,1,2.
968             sub _net_wm_source_num {
969 0     0     my ($X, $source) = @_;
970 0 0         if (! defined $source) {
971 0           return 1; # default "normal"
972             }
973 0 0         if ($source =~ /^\d+$/) {
974 0           return $source; # a number already
975             }
976 0           my $num = $_net_wm_source_num{$source};
977 0 0         if (defined $num) {
978 0           return $num;
979             }
980 0           croak 'Unrecognized _NET_WM source: ',$source;
981             }
982             }
983              
984             sub change_net_wm_state {
985 0     0 1   my ($X, $window, $action, $state, %h) = @_;
986             ### change_net_wm_state() ...
987             ### $state
988             ### %h
989              
990             my $root = X11::Protocol::WM::_root_for_window($X,$window,
991 0           delete $h{'root'});
992 0           my $state2 = _net_wm_state_num($X, delete $h{'state2'});
993 0           my $source = _net_wm_source_num($X, delete $h{'source'});
994 0 0         if (%h) {
995 0           croak "change_net_wm_state() unrecognised parameter(s): ",
996             join(',',keys %h);
997             }
998 0           X11::Protocol::WM::_send_event_to_wm ($X, $root,
999             name => 'ClientMessage',
1000             window => $window,
1001             type => $X->atom('_NET_WM_STATE'),
1002             format => 32,
1003             data => pack('L5',
1004             _net_wm_state_action_num($X, $action),
1005             _net_wm_state_num($X, $state),
1006             $state2,
1007             $source));
1008             }
1009              
1010             #------------------------------------------------------------------------------
1011             # _NET_WM_WINDOW_TYPE
1012              
1013             sub set_net_wm_window_type {
1014 0     0 1   my ($X, $window, $window_type) = @_;
1015 0           _set_card32_property ($X,
1016             $window,
1017             $X->atom('_NET_WM_WINDOW_TYPE'),
1018             X11::AtomConstants::ATOM(),
1019             _net_wm_window_type_to_atom ($X, $window_type));
1020             }
1021              
1022             # not documented yet ...
1023             sub _net_wm_window_type_to_atom {
1024 0     0     my ($X, $window_type) = @_;
1025 0 0 0       if (! defined $window_type || $window_type =~ /^\d+$/) {
1026 0           return $window_type;
1027             } else {
1028 0           return $X->atom ("_NET_WM_WINDOW_TYPE_$window_type");
1029             }
1030             }
1031              
1032             # unless ($window_type =~ /^_NET_WM/) {
1033             # }
1034             # my ($akey, $atype) = _atoms ($X,
1035             # '_NET_WM_WINDOW_TYPE',
1036             # "_NET_WM_WINDOW_TYPE_$window_type");
1037             # a type stringcan be an atom integer, a full atom name like
1038             # "_NET_WM_WINDOW_TYPE_NORMAL", or just the type part "NORMAL". The types in
1039             # the EWMH spec are
1040              
1041              
1042             #------------------------------------------------------------------------------
1043             # _NET_WM_USER_TIME
1044              
1045             sub set_net_wm_user_time {
1046 0     0 1   my ($X, $window, $time) = @_;
1047 0           _set_card32_property ($X,
1048             $window,
1049             $X->atom('_NET_WM_USER_TIME'),
1050             X11::AtomConstants::CARDINAL(),
1051             $time);
1052             }
1053              
1054             #------------------------------------------------------------------------------
1055             # WM_NORMAL_HINTS
1056              
1057             sub set_wm_normal_hints {
1058 0     0 1   my $X = shift;
1059 0           my $window = shift;
1060 0           $X->ChangeProperty($window,
1061             X11::AtomConstants::WM_NORMAL_HINTS(), # property
1062             X11::AtomConstants::WM_SIZE_HINTS(), # type
1063             32, # format
1064             'Replace',
1065             pack_wm_size_hints ($X, @_));
1066             }
1067              
1068             {
1069             my %key_to_flag =
1070             (user_position => 1, # user-specified window x,y
1071             user_size => 2, # user-specified win width,height
1072             program_position => 4, # program-specified window x,y
1073             program_size => 8, # program-specified win width,height
1074             min_width => 16,
1075             min_height => 16,
1076             max_width => 32,
1077             max_height => 32,
1078             width_inc => 64,
1079             height_inc => 64,
1080             min_aspect => 128,
1081             min_aspect_num => 128,
1082             min_aspect_den => 128,
1083             max_aspect => 128,
1084             max_aspect_num => 128,
1085             max_aspect_den => 128,
1086             base_width => 256,
1087             base_height => 256,
1088             win_gravity => 512,
1089             );
1090             sub pack_wm_size_hints {
1091 0     0 0   my ($X, %hint) = @_;
1092             ### pack_wm_size_hints(): %hint
1093              
1094 0           my $flags = 0;
1095 0           my $key;
1096 0           foreach $key (keys %hint) {
1097 0 0         if (defined $hint{$key}) {
1098 0           $flags |= $key_to_flag{$key};
1099             } else {
1100 0           croak "Unrecognised WM_NORMAL_HINTS field: ",$key;
1101             }
1102             }
1103             pack ('Lx16L13',
1104             $flags,
1105             $hint{'min_width'} || 0, # 1
1106             $hint{'min_height'} || 0, # 2
1107             $hint{'max_width'} || 0, # 3
1108             $hint{'max_height'} || 0, # 4
1109             $hint{'width_inc'} || 0, # 5
1110             $hint{'height_inc'} || 0, # 6
1111             _aspect (\%hint, 'min'), # 7,8
1112             _aspect (\%hint, 'max'), # 9,10
1113             $hint{'base_width'} || 0, # 11
1114             $hint{'base_height'} || 0, # 12
1115 0   0       $X->num('WinGravity',$hint{'win_gravity'} || 0), # 13
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
1116             );
1117             }
1118             }
1119             sub _aspect {
1120 0     0     my ($hint, $which) = @_;
1121 0 0         if (defined (my $aspect = $hint->{"${which}_aspect"})) {
1122 0           return aspect_to_num_den($aspect);
1123             } else {
1124             return ($hint->{"${which}_aspect_num"} || 0,
1125 0   0       $hint->{"${which}_aspect_den"} || 0);
      0        
1126             }
1127             }
1128             sub aspect_to_num_den {
1129 0     0 1   my ($aspect) = @_;
1130             ### $aspect
1131              
1132 0           my ($num, $den);
1133              
1134 0 0         if ($aspect =~ /^\d+$/) {
    0          
1135             ### integer
1136 0           $num = $aspect;
1137 0           $den = 1;
1138             } elsif (($num,$den) = ($aspect =~ m{(.*)/(.*)})) {
1139             ### slash fraction
1140             } else {
1141 0           $num = $aspect;
1142 0           $den = 1;
1143             }
1144              
1145 0           my $den_zeros = 0;
1146 0 0         if ($num =~ /^0*(\d*)\.(\d*?)0*$/) {
1147             ### decimal
1148 0           $num = "$1$2";
1149 0           $den_zeros = length($2);
1150             }
1151 0 0         if ($den =~ /^0*(\d*)\.(\d*?)0*$/) {
1152             ### decimal
1153 0           $den = "$1$2";
1154 0           $den_zeros -= length($2);
1155             }
1156 0 0         if ($den_zeros > 0) {
1157 0           $den .= '0' x $den_zeros;
1158             }
1159 0 0         if ($den_zeros < 0) {
1160 0           $num .= '0' x -$den_zeros;
1161             }
1162              
1163 0 0         if ($num == $num-1) { # infinity
1164 0 0         return (0x7FFF_FFFF, ($den == $den-1 # infinity too
1165             ? 0x7FFF_FFFF : 1));
1166             }
1167 0 0         if ($den == $den-1) { # infinity
1168 0           return (1, 0x7FFF_FFFF);
1169             }
1170              
1171             # cap anything bigger than 0x7FFFFFFF
1172 0 0 0       if ($num >= $den && $num > 0x7FFF_FFFF) {
1173             ### reduce big numerator
1174 0           ($num,$den) = _aspect_reduce($num,$den);
1175             }
1176 0 0         if ($den > 0x7FFF_FFFF) {
1177             ### reduce big denominator
1178 0           ($den,$num) = _aspect_reduce($den,$num);
1179             }
1180              
1181             # increase non-integers in binary
1182 0   0       while ((int($num) != $num || int($den) != $den)
      0        
      0        
1183             && $num < 0x4000_0000
1184             && $den < 0x4000_0000) {
1185 0           $num *= 2;
1186 0           $den *= 2;
1187             ### up to: $num,$den
1188             }
1189              
1190 0           return (_round_nz($num), _round_nz($den));
1191             }
1192              
1193             # Return $x rounded to the nearest integer.
1194             # If $x is not zero then the return is not zero too, ie. $x<0.5 is rounded
1195             # up to return 1.
1196             sub _round_nz {
1197 0     0     my ($x) = @_;
1198 0           my $nz = ($x != 0);
1199 0           $x = int ($x + 0.5);
1200 0 0 0       if ($nz && $x == 0) {
1201 0           return 1;
1202             } else {
1203 0           return $x;
1204             }
1205             }
1206              
1207             # $x is > 0x7FFF_FFFF. Reduce it to 0x7FFF_FFFF and reduce $y in proportion.
1208             # If $y!=0 then it's reduced to a minimum 1, not to 0.
1209             sub _aspect_reduce {
1210 0     0     my ($x,$y) = @_;
1211 0           my $nz = ($y != 0);
1212 0           $y = int (0.5 + $y / $x * 0x7FFF_FFFF);
1213 0 0 0       if ($nz && $y == 0) { $y = 1; }
  0 0          
1214 0           elsif ($y > 0x7FFF_FFFF) { $y = 0x7FFF_FFFF; }
1215 0           return (0x7FFF_FFFF, $y);
1216             }
1217             # printf "%d %d", _aspect_frac('.123456789');
1218              
1219              
1220              
1221             1;
1222             __END__