/usr/share/pyshared/wimpiggy/window.py is in python-wimpiggy 0.0.7.36+dfsg-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 | # This file is part of Parti.
# Copyright (C) 2008, 2009 Nathaniel Smith <njs@pobox.com>
# Parti is released under the terms of the GNU GPL v2, or, at your option, any
# later version. See the file COPYING for details.
"""The magic GTK widget that represents a client window.
Most of the gunk required to be a valid window manager (reparenting, synthetic
events, mucking about with properties, etc. etc.) is wrapped up in here."""
# Maintain compatibility with old versions of Python, while avoiding a
# deprecation warning on new versions:
import sys
if sys.version_info < (2, 6):
from sets import ImmutableSet
else:
ImmutableSet = frozenset
import gobject
import gtk.gdk
import cairo
import math
import os
from socket import gethostname
from wimpiggy.lowlevel import (
const, #@UnresolvedImport
add_event_receiver, #@UnresolvedImport
remove_event_receiver, #@UnresolvedImport
get_display_for, #@UnresolvedImport
geometry_with_border, #@UnresolvedImport
calc_constrained_size, #@UnresolvedImport
is_mapped, #@UnresolvedImport
show_unraised_without_extra_stupid_stuff, #@UnresolvedImport
unmap_with_serial, #@UnresolvedImport
XDeleteProperty, #@UnresolvedImport
XAddToSaveSet, #@UnresolvedImport
XRemoveFromSaveSet, #@UnresolvedImport
XSetInputFocus, #@UnresolvedImport
XKillClient, #@UnresolvedImport
send_wm_take_focus, #@UnresolvedImport
send_wm_delete_window, #@UnresolvedImport
sendConfigureNotify, #@UnresolvedImport
configureAndNotify, #@UnresolvedImport
substructureRedirect #@UnresolvedImport
)
from wimpiggy.util import (AutoPropGObjectMixin,
one_arg_signal,
non_none_list_accumulator)
from wimpiggy.error import trap, XError
from wimpiggy.prop import prop_get, prop_set
from wimpiggy.composite import CompositeHelper
from wimpiggy.log import Logger
log = Logger()
# Todo:
# client focus hints
# _NET_WM_SYNC_REQUEST
# root window requests (pagers, etc. requesting to change client states)
# _NET_WM_PING/detect window not responding (also a root window message)
# Okay, we need a block comment to explain the window arrangement that this
# file is working with.
#
# +--------+
# | widget |
# +--------+
# / \
# <- top / -\- bottom ->
# / \
# +-------+ |
# | image | +---------+
# +-------+ | corral |
# +---------+
# |
# +---------+
# | client |
# +---------+
#
# Each box in this diagram represents one X/GDK window. In the common case,
# every window here takes up exactly the same space on the screen (!). In
# fact, the two windows on the right *always* have exactly the same size and
# location, and the window on the left and the top window also always have
# exactly the same size and position. However, each window in the diagram
# plays a subtly different role.
#
# The client window is obvious -- this is the window owned by the client,
# which they created and which we have various ICCCM/EWMH-mandated
# responsibilities towards. It is also composited.
#
# The purpose of the 'corral' is to keep the client window managed -- we
# select for SubstructureRedirect on it, so that the client cannot resize
# etc. without going through the WM.
#
# These two windows are always managed together, as a unit; an invariant of
# the code is that they always take up exactly the same space on the screen.
# They get reparented back and forth between widgets, and when there are no
# widgets, they get reparented to a "parking area". For now, we're just using
# the root window as a parking area, so we also map/unmap the corral window
# depending on whether we are parked or not; the corral and window is left
# mapped at all times.
#
# When a particular WindowView controls the underlying client window, then two
# things happen:
# -- Its size determines the size of the client window. Ideally they are
# the same size -- but this is not always the case, because the client
# may have specified sizing constraints, in which case the client window
# is the "best fit" to the controlling widget window.
# -- The client window and its corral are reparented under the widget
# window, as in the diagram above. This is necessary to allow mouse
# events to work -- a WindowView widget can always *look* like the client
# window is there, through the magic of Composite, but in order for it to
# *act* like the client window is there in terms of receiving mouse
# events, it has to actually be there.
#
# Finally, there is the 'image' window. This is a window that always remains
# in the widget window, and is used to draw what the client currently looks
# like. It needs to receive expose events so it knows if it has been exposed
# (not just when the window it is displaying has changed), and the easiest way
# to arrange for this is to make it exactly the same size as the parent
# 'widget' window. Then the widget window never receives expose events
# (because it is occluded), and we can arrange for the image window's expose
# events to be delivered to the WindowView widget, and they will be in the
# right coordinate space. If the widget is controlling the client, then the
# image window goes on top of the client window. Why don't we just draw onto
# the widget window? Because there is no way to ask Cairo to use
# IncludeInferiors drawing mode -- so if we were drawing onto the widget
# window, and the client were present in the widget window, then the blank
# black 'expose catcher' window would obscure the image of the client.
#
# All clear?
# We should also have a block comment describing how to create a
# view/"controller" for a WindowModel.
#
# Viewing a (Base)WindowModel is easy. Connect to the client-contents-changed
# signal. Every time the window contents is updated, you'll get a message.
# This message is passed a single object e, which has useful members:
# e.x, e.y, e.width, e.height:
# The part of the client window that was modified, and needs to be
# redrawn.
# To get the actual contents of the window to draw, there is a "handle"
# available as the "client-contents-handle" property on the
# (Base)WindowModel. So long as you hold a reference to this object, the
# window contents will be available in its ".pixmap" member. The pixmap
# itself is also available as the "client-contents" property of the
# (Base)WindowModel. The pixmap object will be destroyed as soon as the
# handle object leaves scope, so if you want to hold onto the pixmap for some
# reason (animating a fade when a window is unmapped or whatever) then make
# sure to hold a reference to the handle.
#
# But what if you'd like to do more than just look at your pretty composited
# windows? Maybe you'd like to, say, *interact* with them? Then life is a
# little more complicated. To make a view "live", we have to move the actual
# client window to be a child of your view window and position it correctly.
# Obviously, only one view can be live at any given time, so we have to figure
# out which one that is. Supposing we have a WindowModel called "model" and
# a view called "view", then the following pieces come into play:
# The "ownership-election" signal on window:
# If a view wants the chance to become live, it must connect to this
# signal. When the signal is emitted, its handler should return a tuple
# of the form:
# (votes, my_view)
# Just like a real election, everyone votes for themselves. The view that
# gives the highest value to 'votes' becomes the new owner. However, a
# view with a negative (< 0) votes value will never become the owner.
# model.ownership_election():
# This method (distinct from the ownership-election signal!) triggers an
# election. All views MUST call this method whenever they decide their
# number of votes has changed. All views MUST call this method when they
# are destructing themselves (ideally after disconnecting from the
# ownership-election signal).
# The "owner" property on window:
# This records the view that currently owns the window (i.e., the winner
# of the last election), or None if no view is live.
# view.take_window(model, window):
# This method is called on 'view' when it becomes owner of 'model'. It
# should reparent 'window' into the appropriate place, and put it at the
# appropriate place in its window stack. (The x,y position, however, does
# not matter.)
# view.window_size(model):
# This method is called when the model needs to know how much space it is
# allocated. It should return the maximum (width, height) allowed.
# (However, the model may choose to use less than this.)
# view.window_position(mode, width, height):
# This method is called when the model needs to know where it should be
# located (relative to the parent window the view placed it in). 'width'
# and 'height' are the size the model window will actually be. It should
# return the (x, y) position desired.
# model.maybe_recalculate_geometry_for(view):
# This method (potentially) triggers a resize/move of the client window
# within the view. If 'view' is not the current owner, is a no-op, which
# means that views can call it without worrying about whether they are in
# fact the current owner.
#
# The actual method for choosing 'votes' is not really determined yet.
# Probably it should take into account at least the following factors:
# -- has focus (or has mouse-over?)
# -- is visible in a tray/other window, and the tray/other window is visible
# -- and is focusable
# -- and is not focusable
# -- is visible in a tray, and the tray/other window is not visible
# -- and is focusable
# -- and is not focusable
# (NB: Widget.get_ancestor(my.Tray) will give us the nearest ancestor
# that isinstance(my.Tray), if any.)
# -- is not visible
# -- the size of the widget (as a final tie-breaker)
class Unmanageable(Exception):
pass
class BaseWindowModel(AutoPropGObjectMixin, gobject.GObject):
__gproperties__ = {
"client-window": (gobject.TYPE_PYOBJECT,
"gtk.gdk.Window representing the client toplevel", "",
gobject.PARAM_READABLE),
"geometry": (gobject.TYPE_PYOBJECT,
"current (border-corrected, relative to parent) coordinates (x, y, w, h) for the window", "",
gobject.PARAM_READABLE),
"transient-for": (gobject.TYPE_PYOBJECT,
"Transient for (or None)", "",
gobject.PARAM_READABLE),
"window-type": (gobject.TYPE_PYOBJECT,
"Window type",
"NB, most preferred comes first, then fallbacks",
gobject.PARAM_READABLE),
# NB "notify" signal never fires for the client-contents properties:
"client-contents": (gobject.TYPE_PYOBJECT,
"gtk.gdk.Pixmap containing the window contents", "",
gobject.PARAM_READABLE),
"client-contents-handle": (gobject.TYPE_PYOBJECT,
"", "",
gobject.PARAM_READABLE),
}
__gsignals__ = {
"client-contents-changed": one_arg_signal,
"unmanaged": one_arg_signal,
"wimpiggy-configure-event": one_arg_signal,
}
def __init__(self, client_window):
super(BaseWindowModel, self).__init__()
log("new window %s", hex(client_window.xid))
self._composite = None
self.client_window = client_window
self._internal_set_property("client-window", client_window)
add_event_receiver(client_window, self)
def setup():
# Keith Packard says that composite state is undefined following a
# reparent, so I'm not sure doing this here in the superclass,
# before we reparent, actually works... let's wait and see.
self._composite = CompositeHelper(self.client_window, False)
h = self._composite.connect("contents-changed",
self._forward_contents_changed)
self._damage_forward_handle = h
self._geometry = geometry_with_border(self.client_window)
try:
trap.call(setup)
except XError, e:
raise Unmanageable, e
def _forward_contents_changed(self, obj, event):
self.emit("client-contents-changed", event)
def do_get_property_client_contents(self, name):
return self._composite.get_property("contents")
def do_get_property_client_contents_handle(self, name):
return self._composite.get_property("contents-handle")
def acknowledge_changes(self):
self._composite.acknowledge_changes()
def do_wimpiggy_configure_event(self, event):
self._geometry = (event.x, event.y, event.width, event.height,
event.border_width)
self.notify("geometry")
def do_get_property_geometry(self, pspec):
(x, y, w, h, b) = self._geometry
return (x, y, w + 2*b, h + 2*b)
def unmanage(self, exiting=False):
self.emit("unmanaged", exiting)
def do_unmanaged(self, wm_exiting):
remove_event_receiver(self.client_window, self)
if self._composite:
self._composite.disconnect(self._damage_forward_handle)
self._composite.destroy()
def _read_initial_properties(self):
transient_for = self.prop_get("WM_TRANSIENT_FOR", "window")
# May be None
self._internal_set_property("transient-for", transient_for)
window_types = self.prop_get("_NET_WM_WINDOW_TYPE", ["atom"])
if window_types:
self._internal_set_property("window-type", window_types)
else:
if transient_for is not None:
# EWMH says that even if it's transient-for, we MUST check to
# see if it's override-redirect (and if so treat as NORMAL).
# But we wouldn't be here if this was override-redirect.
assume_type = "_NET_WM_TYPE_DIALOG"
else:
assume_type = "_NET_WM_WINDOW_TYPE_NORMAL"
self._internal_set_property("window-type",
[gtk.gdk.atom_intern(assume_type)])
gobject.type_register(BaseWindowModel)
# FIXME: EWMH says that O-R windows should set various properties just like
# ordinary managed windows; so some of that code should get pushed up into the
# superclass sooner or later. When someone cares, presumably.
class OverrideRedirectWindowModel(BaseWindowModel):
__gsignals__ = {
"wimpiggy-unmap-event": one_arg_signal,
}
def __init__(self, client_window):
BaseWindowModel.__init__(self, client_window)
def setup():
self.client_window.set_events(self.client_window.get_events()
| gtk.gdk.STRUCTURE_MASK)
# So now if the window becomes unmapped in the future then we will
# notice... but it might be unmapped already, and any event
# already generated, and our request for that event is too late!
# So double check now, *after* putting in our request:
if not is_mapped(self.client_window):
raise Unmanageable, "window already unmapped"
self._read_initial_properties()
try:
trap.call(setup)
except XError, e:
raise Unmanageable, e
def do_wimpiggy_unmap_event(self, event):
self.unmanage()
def prop_get(self, key, type):
return prop_get(self.client_window, key, type, ignore_errors=False)
gobject.type_register(OverrideRedirectWindowModel)
class WindowModel(BaseWindowModel):
"""This represents a managed client window. It allows one to produce
widgets that view that client window in various ways."""
_NET_WM_ALLOWED_ACTIONS = [
"_NET_WM_ACTION_CLOSE",
]
__gproperties__ = {
# Interesting properties of the client window, that will be
# automatically kept up to date:
"attention-requested": (gobject.TYPE_BOOLEAN,
"Urgency hint from client, or us", "",
False,
gobject.PARAM_READWRITE),
"fullscreen": (gobject.TYPE_BOOLEAN,
"Fullscreen-ness of window", "",
False,
gobject.PARAM_READWRITE),
"actual-size": (gobject.TYPE_PYOBJECT,
"Size of client window (actual (width,height))", "",
gobject.PARAM_READABLE),
"user-friendly-size": (gobject.TYPE_PYOBJECT,
"Description of client window size for user", "",
gobject.PARAM_READABLE),
"requested-position": (gobject.TYPE_PYOBJECT,
"Client-requested position on screen", "",
gobject.PARAM_READABLE),
"requested-size": (gobject.TYPE_PYOBJECT,
"Client-requested size on screen", "",
gobject.PARAM_READABLE),
"size-hints": (gobject.TYPE_PYOBJECT,
"Client hints on constraining its size", "",
gobject.PARAM_READABLE),
"strut": (gobject.TYPE_PYOBJECT,
"Struts requested by window, or None", "",
gobject.PARAM_READABLE),
"class-instance": (gobject.TYPE_PYOBJECT,
"Classic X 'class' and 'instance'", "",
gobject.PARAM_READABLE),
"protocols": (gobject.TYPE_PYOBJECT,
"Supported WM protocols", "",
gobject.PARAM_READABLE),
"pid": (gobject.TYPE_INT,
"PID of owning process", "",
-1, 65535, -1,
gobject.PARAM_READABLE),
"client-machine": (gobject.TYPE_PYOBJECT,
"Host where client process is running", "",
gobject.PARAM_READABLE),
"group-leader": (gobject.TYPE_PYOBJECT,
"Window group leader (opaque identifier)", "",
gobject.PARAM_READABLE),
# Toggling this property does not actually make the window iconified,
# i.e. make it appear or disappear from the screen -- it merely
# updates the various window manager properties that inform the world
# whether or not the window is iconified.
"iconic": (gobject.TYPE_BOOLEAN,
"ICCCM 'iconic' state -- any sort of 'not on desktop'.", "",
False,
gobject.PARAM_READWRITE),
"can-focus": (gobject.TYPE_BOOLEAN,
"Does this window ever accept keyboard input?", "",
True,
gobject.PARAM_READWRITE),
"state": (gobject.TYPE_PYOBJECT,
"State, as per _NET_WM_STATE", "",
gobject.PARAM_READABLE),
"title": (gobject.TYPE_PYOBJECT,
"Window title (unicode or None)", "",
gobject.PARAM_READABLE),
"icon-title": (gobject.TYPE_PYOBJECT,
"Icon title (unicode or None)", "",
gobject.PARAM_READABLE),
"icon": (gobject.TYPE_PYOBJECT,
"Icon (local Cairo surface)", "",
gobject.PARAM_READABLE),
"icon-pixmap": (gobject.TYPE_PYOBJECT,
"Icon (server Pixmap)", "",
gobject.PARAM_READABLE),
"owner": (gobject.TYPE_PYOBJECT,
"Owner", "",
gobject.PARAM_READABLE),
}
__gsignals__ = {
# X11 bell event:
"bell": one_arg_signal,
"ownership-election": (gobject.SIGNAL_RUN_LAST,
gobject.TYPE_PYOBJECT, (),
non_none_list_accumulator),
"child-map-request-event": one_arg_signal,
"child-configure-request-event": one_arg_signal,
"wimpiggy-property-notify-event": one_arg_signal,
"wimpiggy-unmap-event": one_arg_signal,
"wimpiggy-destroy-event": one_arg_signal,
"wimpiggy-xkb-event": one_arg_signal,
}
def __init__(self, parking_window, client_window):
"""Register a new client window with the WM.
Raises an Unmanageable exception if this window should not be
managed, for whatever reason. ATM, this mostly means that the window
died somehow before we could do anything with it."""
BaseWindowModel.__init__(self, client_window)
self.parking_window = parking_window
self._setup_done = False
self.connect("notify::iconic", self._handle_iconic_update)
# We enable PROPERTY_CHANGE_MASK so that we can call
# x11_get_server_time on this window.
self.corral_window = gtk.gdk.Window(self.parking_window,
width=100,
height=100,
window_type=gtk.gdk.WINDOW_CHILD,
wclass=gtk.gdk.INPUT_OUTPUT,
event_mask=gtk.gdk.PROPERTY_CHANGE_MASK)
substructureRedirect(self.corral_window)
add_event_receiver(self.corral_window, self)
log("created corral window 0x%x", self.corral_window.xid)
# The WM_HINTS input field
self._input_field = True
def setup_client():
# Start listening for important events.
self.client_window.set_events(self.client_window.get_events()
| gtk.gdk.STRUCTURE_MASK
| gtk.gdk.PROPERTY_CHANGE_MASK)
# The child might already be mapped, in case we inherited it from
# a previous window manager. If so, we unmap it now, and save the
# serial number of the request -- this way, when we get an
# UnmapNotify later, we'll know that it's just from us unmapping
# the window, not from the client withdrawing the window.
self.startup_unmap_serial = None
if is_mapped(self.client_window):
log("hiding inherited window")
self.startup_unmap_serial = unmap_with_serial(self.client_window)
# Process properties
self._read_initial_properties()
self._write_initial_properties_and_setup()
# For now, we never use the Iconic state at all.
self._internal_set_property("iconic", False)
XAddToSaveSet(self.client_window)
self.client_window.reparent(self.corral_window, 0, 0)
client_size = self.client_window.get_geometry()[2:4]
self.corral_window.resize(*client_size)
# We CANNOT use .show_unraised here, because of GTK+ bug #526635:
show_unraised_without_extra_stupid_stuff(self.client_window)
try:
trap.call(setup_client)
except XError, e:
raise Unmanageable, e
self._setup_done = True
def prop_get(self, key, type, ignore_errors=False):
# Utility wrapper for prop_get on the client_window
# also allows us to ignore property errors during setup_client
if not self._setup_done:
ignore_errors = True
return prop_get(self.client_window, key, type, ignore_errors=ignore_errors)
def do_wimpiggy_xkb_event(self, event):
log("WindowModel.do_wimpiggy_xkb_event(%r)" % event)
if event.type!="bell":
log.error("WindowModel.do_wimpiggy_xkb_event(%r) unknown event type: %s" % (event, event.type))
return
event.window_model = self
self.emit("bell", event)
def do_child_map_request_event(self, event):
# If we get a MapRequest then it might mean that someone tried to map
# this window multiple times in quick succession, before we actually
# mapped it (so that several MapRequests ended up queued up; FSF Emacs
# 22.1.50.1 does this, at least). It alternatively might mean that
# the client is naughty and tried to map their window which is
# currently not displayed. In either case, we should just ignore the
# request.
pass
def do_wimpiggy_unmap_event(self, event):
if event.delivered_to is self.corral_window:
return
assert event.window is self.client_window
# The client window got unmapped. The question is, though, was that
# because it was withdrawn/destroyed, or was it because we unmapped it
# going into IconicState?
#
# At the moment, we never actually put windows into IconicState
# (i.e. unmap them), except in the special case when we start up and
# find windows that are already mapped. So we only need to check
# against that one serial number.
#
# Also, if we receive a *synthetic* UnmapNotify event, that always
# means that the client has withdrawn the window (even if it was not
# mapped in the first place) -- ICCCM section 4.1.4.
log("Client window unmapped")
if event.send_event or event.serial != self.startup_unmap_serial:
self.unmanage()
def do_wimpiggy_destroy_event(self, event):
if event.delivered_to is self.corral_window:
return
assert event.window is self.client_window
# This is somewhat redundant with the unmap signal, because if you
# destroy a mapped window, then a UnmapNotify is always generated.
# However, this allows us to catch the destruction of unmapped
# ("iconified") windows, and also catch any mistakes we might have
# made with unmap heuristics. I love the smell of XDestroyWindow in
# the morning. It makes for simple code:
self.unmanage()
def do_unmanaged(self, exiting):
log("unmanaging window")
self._internal_set_property("owner", None)
def unmanageit():
self._scrub_withdrawn_window()
self.client_window.reparent(gtk.gdk.get_default_root_window(),
0, 0)
# It is important to remove from our save set, even after
# reparenting, because according to the X spec, windows that are
# in our save set are always Mapped when we exit, *even if those
# windows are no longer inferior to any of our windows!* (see
# section 10. Connection Close). This causes "ghost windows", see
# bug #27:
XRemoveFromSaveSet(self.client_window)
sendConfigureNotify(self.client_window)
if exiting:
# We CANNOT use .show_unraised here, because of GTK+ bug
# #526635:
show_unraised_without_extra_stupid_stuff(self.client_window)
trap.swallow(unmanageit)
self.corral_window.destroy()
BaseWindowModel.do_unmanaged(self, exiting)
def ownership_election(self):
candidates = self.emit("ownership-election")
if candidates:
rating, winner = sorted(candidates)[-1]
if rating < 0:
winner = None
else:
winner = None
old_owner = self.get_property("owner")
if old_owner is winner:
return
if old_owner is not None:
self.corral_window.hide()
self.corral_window.reparent(self.parking_window, 0, 0)
self._internal_set_property("owner", winner)
if winner is not None:
winner.take_window(self, self.corral_window)
self._update_client_geometry()
self.corral_window.show_unraised()
trap.swallow(sendConfigureNotify, self.client_window)
def maybe_recalculate_geometry_for(self, maybe_owner):
if maybe_owner and self.get_property("owner") is maybe_owner:
self._update_client_geometry()
def _sanitize_size_hints(self, size_hints):
if size_hints is None:
return
for attr in ["max_size", "min_size", "base_size",
"resize_inc", "min_aspect", "max_aspect"]:
#"min_aspect_ratio", "max_aspect_ratio" ??
v = getattr(size_hints, attr)
if v is not None:
w,h = v
if w>=(2**32-1) or h>(2**32-1):
log.info("clearing invalid size hint value for %s: %s", attr, v)
setattr(size_hints, attr, (-1,-1))
def _update_client_geometry(self):
owner = self.get_property("owner")
if owner is not None:
(allocated_w, allocated_h) = owner.window_size(self)
hints = self.get_property("size-hints")
self._sanitize_size_hints(hints)
size = calc_constrained_size(allocated_w, allocated_h, hints)
(w, h, wvis, hvis) = size
(x, y) = owner.window_position(self, w, h)
self.corral_window.move_resize(x, y, w, h)
trap.swallow(configureAndNotify, self.client_window, 0, 0, w, h)
self._internal_set_property("actual-size", (w, h))
self._internal_set_property("user-friendly-size", (wvis, hvis))
def do_child_configure_request_event(self, event):
# Ignore the request, but as per ICCCM 4.1.5, send back a synthetic
# ConfigureNotify telling the client that nothing has happened.
trap.swallow(sendConfigureNotify, event.window)
# Also potentially update our record of what the app has requested:
(x, y) = self.get_property("requested-position")
if event.value_mask & const["CWX"]:
x = event.x
if event.value_mask & const["CWY"]:
y = event.y
self._internal_set_property("requested-position", (x, y))
(w, h) = self.get_property("requested-size")
if event.value_mask & const["CWWidth"]:
w = event.width
if event.value_mask & const["CWHeight"]:
h = event.height
self._internal_set_property("requested-size", (w, h))
self._update_client_geometry()
# FIXME: consider handling attempts to change stacking order here.
# (In particular, I believe that a request to jump to the top is
# meaningful and should perhaps even be respected.)
################################
# Property reading
################################
def do_wimpiggy_property_notify_event(self, event):
if event.delivered_to is self.corral_window:
return
assert event.window is self.client_window
self._handle_property_change(str(event.atom))
_property_handlers = {}
def _handle_property_change(self, name):
log("Property changed on %s: %s", self.client_window.xid, name)
if name in self._property_handlers:
self._property_handlers[name](self)
def _handle_wm_hints(self):
wm_hints = self.prop_get("WM_HINTS", "wm-hints", True)
if wm_hints is not None:
# GdkWindow or None
self._internal_set_property("group-leader", wm_hints.group_leader)
# FIXME: extract state and input hint
if wm_hints.urgency:
self.set_property("attention-requested", True)
log("wm_hints.input = %s", wm_hints.input)
if wm_hints.input is not None:
self._input_field = wm_hints.input
self.notify("can-focus")
_property_handlers["WM_HINTS"] = _handle_wm_hints
def _handle_wm_normal_hints(self):
size_hints = self.prop_get("WM_NORMAL_HINTS", "wm-size-hints")
# Don't send out notify and ConfigureNotify events when this property
# gets no-op updated -- some apps like FSF Emacs 21 like to update
# their properties every time they see a ConfigureNotify, and this
# reduces the chance for us to get caught in loops:
old_hints = self.get_property("size-hints")
if old_hints is None or size_hints.__dict__ != old_hints.__dict__:
self._internal_set_property("size-hints", size_hints)
self._update_client_geometry()
_property_handlers["WM_NORMAL_HINTS"] = _handle_wm_normal_hints
def _handle_title_change(self):
wm_name = self.prop_get("WM_NAME", "latin1", True)
net_wm_name = self.prop_get("_NET_WM_NAME", "utf8", True)
if net_wm_name is not None:
self._internal_set_property("title", net_wm_name)
else:
# may be None
self._internal_set_property("title", wm_name)
_property_handlers["WM_NAME"] = _handle_title_change
_property_handlers["_NET_WM_NAME"] = _handle_title_change
def _handle_icon_title_change(self):
wm_icon_name = self.prop_get("WM_ICON_NAME", "latin1", True)
net_wm_icon_name = self.prop_get("_NET_WM_ICON_NAME", "utf8", True)
if net_wm_icon_name is not None:
self._internal_set_property("icon-title", net_wm_icon_name)
else:
# may be None
self._internal_set_property("icon-title", wm_icon_name)
_property_handlers["WM_ICON_NAME"] = _handle_icon_title_change
_property_handlers["_NET_WM_ICON_NAME"] = _handle_icon_title_change
def _handle_wm_strut(self):
partial = self.prop_get("_NET_WM_STRUT_PARTIAL", "strut-partial")
if partial is not None:
self._internal_set_property("strut", partial)
return
full = self.prop_get("_NET_WM_STRUT", "strut")
# Might be None:
self._internal_set_property("strut", full)
_property_handlers["_NET_WM_STRUT"] = _handle_wm_strut
_property_handlers["_NET_WM_STRUT_PARTIAL"] = _handle_wm_strut
def _handle_net_wm_icon(self):
log("_NET_WM_ICON changed on %s, re-reading", self.client_window.xid)
surf = self.prop_get("_NET_WM_ICON", "icon")
if surf is not None:
# FIXME: There is no Pixmap.new_for_display(), so this isn't
# actually display-clean. Oh well.
pixmap = gtk.gdk.Pixmap(None,
surf.get_width(), surf.get_height(), 32)
screen = get_display_for(pixmap).get_default_screen()
pixmap.set_colormap(screen.get_rgba_colormap())
cr = pixmap.cairo_create()
cr.set_source_surface(surf)
# Important to use SOURCE, because a newly created Pixmap can have
# random trash as its contents, and otherwise that will show
# through any alpha in the icon:
cr.set_operator(cairo.OPERATOR_SOURCE)
cr.paint()
else:
pixmap = None
self._internal_set_property("icon", surf)
self._internal_set_property("icon-pixmap", pixmap)
log("icon is now %r", self.get_property("icon"))
_property_handlers["_NET_WM_ICON"] = _handle_net_wm_icon
def _read_initial_properties(self):
# Things that don't change:
BaseWindowModel._read_initial_properties(self)
geometry = self.client_window.get_geometry()
self._internal_set_property("requested-position", (geometry[0], geometry[1]))
self._internal_set_property("requested-size", (geometry[2], geometry[3]))
def set_class_instance(s):
try:
parts = class_instance.split("\0")
if len(parts)!=3:
return False
(c, i, _) = parts
self._internal_set_property("class-instance", (c, i))
return True
except ValueError:
log.warn("Malformed WM_CLASS: %s, ignoring", class_instance)
return False
class_instance = self.prop_get("WM_CLASS", "latin1")
if class_instance:
if not set_class_instance(class_instance):
set_class_instance(self.prop_get("WM_CLASS", "utf8"))
protocols = self.prop_get("WM_PROTOCOLS", ["atom"])
if protocols is None:
protocols = []
self._internal_set_property("protocols", protocols)
self.notify("can-focus")
pid = self.prop_get("_NET_WM_PID", "u32")
if pid is not None:
self._internal_set_property("pid", pid)
else:
self._internal_set_property("pid", -1)
client_machine = self.prop_get("WM_CLIENT_MACHINE", "latin1")
# May be None
self._internal_set_property("client-machine", client_machine)
# WARNING: have to handle _NET_WM_STATE before we look at WM_HINTS;
# WM_HINTS assumes that our "state" property is already set. This is
# because there are four ways a window can get its urgency
# ("attention-requested") bit set:
# 1) _NET_WM_STATE_DEMANDS_ATTENTION in the _initial_ state hints
# 2) setting the bit WM_HINTS, at _any_ time
# 3) sending a request to the root window to add
# _NET_WM_STATE_DEMANDS_ATTENTION to their state hints
# 4) if we (the wm) decide they should be and set it
# To implement this, we generally track the urgency bit via
# _NET_WM_STATE (since that is under our sole control during normal
# operation). Then (1) is accomplished through the normal rule that
# initial states are read off from the client, and (2) is accomplished
# by having WM_HINTS affect _NET_WM_STATE. But this means that
# WM_HINTS and _NET_WM_STATE handling become intertangled.
net_wm_state = self.prop_get("_NET_WM_STATE", ["atom"])
if net_wm_state:
self._internal_set_property("state", ImmutableSet(net_wm_state))
else:
self._internal_set_property("state", ImmutableSet())
for mutable in ["WM_HINTS", "WM_NORMAL_HINTS",
"WM_NAME", "_NET_WM_NAME",
"WM_ICON_NAME", "_NET_WM_ICON_NAME",
"_NET_WM_STRUT", "_NET_WM_STRUT_PARTIAL",
"_NET_WM_ICON"]:
self._handle_property_change(mutable)
################################
# Property setting
################################
# A few words about _NET_WM_STATE are in order. Basically, it is a set of
# flags. Clients are allowed to set the initial value of this X property
# to anything they like, when their window is first mapped; after that,
# though, only the window manager is allowed to touch this property. So
# we store its value (or at least, our idea as to its value, the X server
# in principle could disagree) as the "state" property. There are
# basically two things we need to accomplish:
# 1) Whenever our property is modified, we mirror that modification into
# the X server. This is done by connecting to our own notify::state
# signal.
# 2) As a more user-friendly interface to these state flags, we provide
# several boolean properties like "attention-requested".
# These are virtual boolean variables; they are actually backed
# directly by the "state" property, and reading/writing them in fact
# accesses the "state" set directly. This is done by overriding
# do_set_property and do_get_property.
_state_properties = {
"attention-requested": "_NET_WM_STATE_DEMANDS_ATTENTION",
"fullscreen": "_NET_WM_STATE_FULLSCREEN",
}
_state_properties_reversed = {}
for k, v in _state_properties.iteritems():
_state_properties_reversed[v] = k
def _state_add(self, state_name):
curr = set(self.get_property("state"))
if state_name not in curr:
curr.add(state_name)
self._internal_set_property("state", ImmutableSet(curr))
if state_name in self._state_properties_reversed:
self.notify(self._state_properties_reversed[state_name])
def _state_remove(self, state_name):
curr = set(self.get_property("state"))
if state_name in curr:
curr.discard(state_name)
self._internal_set_property("state", ImmutableSet(curr))
if state_name in self._state_properties_reversed:
self.notify(self._state_properties_reversed[state_name])
def _state_isset(self, state_name):
return state_name in self.get_property("state")
def _handle_state_changed(self, *args):
# Sync changes to "state" property out to X property.
prop_set(self.client_window, "_NET_WM_STATE",
["atom"], self.get_property("state"))
def do_set_property(self, pspec, value):
if pspec.name in self._state_properties:
state = self._state_properties[pspec.name]
if value:
self._state_add(state)
else:
self._state_remove(state)
else:
AutoPropGObjectMixin.do_set_property(self, pspec, value)
def do_get_property_can_focus(self, name):
assert name == "can-focus"
return self._input_field or "WM_TAKE_FOCUS" in self.get_property("protocols")
def do_get_property(self, pspec):
if pspec.name in self._state_properties:
return self._state_isset(self._state_properties[pspec.name])
else:
return AutoPropGObjectMixin.do_get_property(self, pspec)
def _handle_iconic_update(self, *args):
if self.get_property("iconic"):
trap.swallow(prop_set, self.client_window, "WM_STATE",
["u32"],
[const["IconicState"], const["XNone"]])
self._state_add("_NET_WM_STATE_HIDDEN")
else:
trap.swallow(prop_set, self.client_window, "WM_STATE",
["u32"],
[const["NormalState"], const["XNone"]])
self._state_remove("_NET_WM_STATE_HIDDEN")
def _write_initial_properties_and_setup(self):
# Things that don't change:
prop_set(self.client_window, "_NET_WM_ALLOWED_ACTIONS",
["atom"], self._NET_WM_ALLOWED_ACTIONS)
prop_set(self.client_window, "_NET_FRAME_EXTENTS",
["u32"], [0, 0, 0, 0])
self.connect("notify::state", self._handle_state_changed)
# Flush things:
self._handle_state_changed()
def _scrub_withdrawn_window(self):
remove = ["WM_STATE",
"_NET_WM_STATE",
"_NET_FRAME_EXTENTS",
"_NET_WM_ALLOWED_ACTIONS",
]
def doit():
for prop in remove:
XDeleteProperty(self.client_window, prop)
trap.swallow(doit)
################################
# Focus handling:
################################
def give_client_focus(self):
"""The focus manager has decided that our client should recieve X
focus. See world_window.py for details."""
log("Giving focus to client")
# Have to fetch the time, not just use CurrentTime, both because ICCCM
# says that WM_TAKE_FOCUS must use a real time and because there are
# genuine race conditions here (e.g. suppose the client does not
# actually get around to requesting the focus until after we have
# already changed our mind and decided to give it to someone else).
now = gtk.gdk.x11_get_server_time(self.corral_window)
# ICCCM 4.1.7 *claims* to describe how we are supposed to give focus
# to a window, but it is completely opaque. From reading the
# metacity, kwin, gtk+, and qt code, it appears that the actual rules
# for giving focus are:
# -- the WM_HINTS input field determines whether the WM should call
# XSetInputFocus
# -- independently, the WM_TAKE_FOCUS protocol determines whether
# the WM should send a WM_TAKE_FOCUS ClientMessage.
# If both are set, both methods MUST be used together. For example,
# GTK+ apps respect WM_TAKE_FOCUS alone but I'm not sure they handle
# XSetInputFocus well, while Qt apps ignore (!!!) WM_TAKE_FOCUS
# (unless they have a modal window), and just expect to get focus from
# the WM's XSetInputFocus.
success = True
if self._input_field:
log("... using XSetInputFocus")
if not trap.swallow(XSetInputFocus, self.client_window, now):
log("XSetInputFocus failed...")
success = False
if "WM_TAKE_FOCUS" in self.get_property("protocols"):
log("... using WM_TAKE_FOCUS")
if not trap.swallow(send_wm_take_focus, self.client_window, now):
log("WM_TAKE_FOCUS failed...")
success = False
return success
################################
# Killing clients:
################################
def request_close(self):
if "WM_DELETE_WINDOW" in self.get_property("protocols"):
trap.swallow(send_wm_delete_window, self.client_window)
else:
# You don't wanna play ball? Then no more Mr. Nice Guy!
self.force_quit()
def force_quit(self):
pid = self.get_property("pid")
machine = self.get_property("client-machine")
localhost = gethostname()
if pid > 0 and machine is not None and machine == localhost:
try:
os.kill(pid, 9)
except OSError:
log.warn("failed to kill() client with pid %s", pid)
trap.swallow(XKillClient, self.client_window)
gobject.type_register(WindowModel)
class WindowView(gtk.Widget):
def __init__(self, model):
gtk.Widget.__init__(self)
# Workaround for pygobject bug, see comment in do_destroy():
self._got_inited = True
self._image_window = None
self.model = model
self._redraw_handle = self.model.connect("client-contents-changed",
self._client_contents_changed)
self._election_handle = self.model.connect("ownership-election",
self._vote_for_pedro)
self._unmanaged_handle = self.model.connect("unmanaged",
self._unmanaged)
# Standard GTK double-buffering is useless for us, because it's on our
# "official" window, and we don't draw to that.
self.set_double_buffered(False)
self.set_property("can-focus", self.model.get_property("can-focus"))
def _unmanaged(self, model, wm_is_exiting):
self.destroy()
def do_destroy(self):
# Somehow, it is possible for this method to be called without
# __init__ having been called! What seems to be happening is some
# skew between the Python object and the GObject that it is wrapping.
# The pygobject library stashes a pointer to the Python object inside
# the GObject; normally, when we try to wrap a GObject that already
# has a wrapper, it notices that this stashed pointer is already
# there, and just returns the existing Python object rather than
# creating a new Python wrapper. Somehow, this stashed pointer is
# getting lost -- perhaps because calling destroy() clears out the
# place where it is stashed? -- and then later on the last reference
# to our Python object drops, this causes signal emission on the
# GObject, it tries to reflect back into Python, but the stashed
# pointer is gone, so it ends up creating a new Python wrapper and
# calling methods on it, and things blow up. So let's skip the
# blow-up.
# This is gnome bug #518999.
if not hasattr(self, "_got_inited"):
log.warn("nasty gobject bug #518999 triggered, working around")
return
self.model.disconnect(self._redraw_handle)
self.model.disconnect(self._election_handle)
self.model.disconnect(self._unmanaged_handle)
self.model = None
gtk.Widget.do_destroy(self)
def _invalidate_all(self):
self._image_window.invalidate_rect(gtk.gdk.Rectangle(width=100000,
height=10000),
False)
def _get_transform_matrix(self):
m = cairo.Matrix()
size = self.model.get_property("actual-size")
if self.model.get_property("owner") is self:
m.translate(*self.window_position(self.model, *size))
else:
scale_factor = min(self.allocation[2] * 1.0 / size[0],
self.allocation[3] * 1.0 / size[1])
if 0.95 < scale_factor:
scale_factor = 1
offset = self.window_position(self.model,
size[0] * scale_factor,
size[1] * scale_factor)
m.translate(*offset)
m.scale(scale_factor, scale_factor)
return m
def _vote_for_pedro(self, model):
if self.flags() & gtk.MAPPED:
return (self.allocation.width * self.allocation.height, self)
else:
return (-1, self)
def _client_contents_changed(self, model, event):
if not self.flags() & gtk.MAPPED:
return
m = self._get_transform_matrix()
# This is the right way to convert an integer-space bounding box into
# another integer-space bounding box:
(x1, y1) = m.transform_point(event.x, event.y)
(x2, y2) = m.transform_point(event.x + event.width,
event.y + event.height)
x1i = int(math.floor(x1))
y1i = int(math.floor(y1))
x2i = int(math.ceil(x2))
y2i = int(math.ceil(y2))
transformed = gtk.gdk.Rectangle(x1i, y1i, x2i - x1i, y2i - y1i)
# print("damage (%s, %s, %s, %s) -> expose on (%s, %s, %s, %s)" %
# (event.area.x, event.area.y, event.area.width, event.area.height,
# transformed.x, transformed.y, transformed.width, transformed.height))
self._image_window.invalidate_rect(transformed, False)
def do_expose_event(self, event):
if not self.flags() & gtk.MAPPED:
return
debug = False
if debug:
print("redrawing rectangle at (%s, %s, %s, %s)"
% (event.area.x, event.area.y,
event.area.width, event.area.height))
# Blit the client window in as our image of ourself.
cr = self._image_window.cairo_create()
if not debug:
cr.rectangle(event.area)
cr.clip()
# Create a temporary buffer and draw onto that. It might in some
# vague sense be cleaner (and perhaps slightly less code) to just call
# begin_paint_rect and end_paint on our target window, but this works
# fine.
#
# Note about push_group():
# "<cworth> njs: It's [the temporary buffer push_group allocates] as
# large as the current clip region.
# <cworth> njs: And yes, you'll get a server-side Pixmap when
# targeting an xlib surface."
# Both of which are exactly what we want for double-buffering.
cr.save()
cr.push_group()
# Random grey background:
cr.save()
cr.set_operator(cairo.OPERATOR_SOURCE)
cr.set_source_rgb(0.5, 0.5, 0.5)
cr.paint()
cr.restore()
cr.save()
cr.set_matrix(self._get_transform_matrix())
# It's important to acknowledge changes *before* we redraw them, to
# avoid a race condition.
self.model.acknowledge_changes()
cr.set_source_pixmap(self.model.get_property("client-contents"),
0, 0)
# Super slow (copies everything out of the server and then back
# again), but an option for working around Cairo/X bugs:
#tmpsrf = cairo.ImageSurface(cairo.FORMAT_ARGB32,
# source.get_width(), source.get_height())
#tmpcr = cairo.Context(tmpsrf)
#tmpcr.set_source_surface(source)
#tmpcr.set_operator(cairo.OPERATOR_SOURCE)
#tmpcr.paint()
#cr.set_source_surface(tmpsrf, 0, 0)
cr.paint()
icon = self.model.get_property("icon-pixmap")
if icon is not None:
cr.set_source_pixmap(icon, 0, 0)
cr.paint_with_alpha(0.3)
if debug:
# Overlay a blue square to show where the origin of the
# transformed coordinates is
cr.set_operator(cairo.OPERATOR_OVER)
cr.rectangle(0, 0, 10, 10)
cr.set_source_rgba(0, 0, 1, 0.2)
cr.fill()
cr.restore()
cr.pop_group_to_source()
cr.set_operator(cairo.OPERATOR_SOURCE)
cr.paint()
cr.restore()
if debug:
# Overlay a green rectangle to show the damaged area
cr.save()
cr.rectangle(event.area)
cr.set_source_rgba(0, 1, 0, 0.2)
cr.fill()
cr.restore()
def window_position(self, model, w, h):
assert self.flags() & gtk.REALIZED
# These can come out negative; that's okay.
return ((self.allocation.width - w) // 2,
(self.allocation.height - h) // 2)
def do_size_request(self, requisition):
# FIXME if we ever need to do automatic layout of these sorts of
# widgets. For now the assumption is that we're pretty much always
# going to get a size imposed on us, so no point in spending excessive
# effort figuring out what requisition means in this context.
(requisition.width, requisition.height) = (100, 100)
def do_size_allocate(self, allocation):
self.allocation = allocation
log("New allocation = %r", tuple(self.allocation))
if self.flags() & gtk.REALIZED:
self.window.move_resize(*allocation)
self._image_window.resize(allocation.width, allocation.height)
self._image_window.input_shape_combine_region(gtk.gdk.Region(),
0, 0)
self.model.ownership_election()
self.model.maybe_recalculate_geometry_for(self)
def window_size(self, model):
assert self.flags() & gtk.REALIZED
return (self.allocation.width, self.allocation.height)
def take_window(self, model, window):
assert self.flags() & gtk.REALIZED
window.reparent(self.window, 0, 0)
window.lower()
def do_realize(self):
log("Realizing (allocation = %r)", tuple(self.allocation))
self.set_flags(gtk.REALIZED)
self.window = gtk.gdk.Window(self.get_parent_window(),
width=self.allocation.width,
height=self.allocation.height,
window_type=gtk.gdk.WINDOW_CHILD,
wclass=gtk.gdk.INPUT_OUTPUT,
event_mask=0)
self.window.set_user_data(self)
# Give it a nice theme-defined background
#self.style.attach(self.window)
#self.style.set_background(self.window, gtk.STATE_NORMAL)
self.window.move_resize(*self.allocation)
self._image_window = gtk.gdk.Window(self.window,
width=self.allocation.width,
height=self.allocation.height,
window_type=gtk.gdk.WINDOW_CHILD,
wclass=gtk.gdk.INPUT_OUTPUT,
event_mask=gtk.gdk.EXPOSURE_MASK)
self._image_window.input_shape_combine_region(gtk.gdk.Region(),
0, 0)
self._image_window.set_user_data(self)
self._image_window.show()
log("Realized")
def do_map(self):
assert self.flags() & gtk.REALIZED
if self.flags() & gtk.MAPPED:
return
log("Mapping")
self.set_flags(gtk.MAPPED)
self.model.ownership_election()
self.window.show_unraised()
log("Mapped")
def do_unmap(self):
if not (self.flags() & gtk.MAPPED):
return
log("Unmapping")
self.unset_flags(gtk.MAPPED)
self.window.hide()
self.model.ownership_election()
log("Unmapped")
def do_unrealize(self):
log("Unrealizing")
# Takes care of checking mapped status, issuing signals, calling
# do_unmap, etc.
self.unmap()
self.unset_flags(gtk.REALIZED)
# Break circular reference
if self.window:
self.window.set_user_data(None)
self._image_window = None
log("Unrealized")
gobject.type_register(WindowView)
|